File Coverage

blib/lib/Graphics/Framebuffer.pm
Criterion Covered Total %
statement 254 2462 10.3
branch 49 1190 4.1
condition 26 632 4.1
subroutine 38 129 29.4
pod 1 75 1.3
total 368 4488 8.2


line stmt bran cond sub pod time code
1             package Graphics::Framebuffer;
2              
3              
4              
5             =head1 NAME
6              
7             Graphics::Framebuffer - A Simple Framebuffer Graphics Library
8              
9             =head1 SYNOPSIS
10              
11             Direct drawing for 32/24/16 bit framebuffers (others would be supported if asked for)
12              
13             use Graphics::Framebuffer;
14              
15             our $fb = Graphics::Framebuffer->new();
16              
17             Drawing is this simple
18              
19             $fb->cls('OFF'); # Clear screen and turn off the console cursor
20              
21             $fb->set_color({'red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 255});
22             $fb->plot({'x' => 28, 'y' => 79,'pixel_size' => 1});
23             $fb->drawto({'x' => 405,'y' => 681,'pixel_size' => 1});
24             $fb->circle({'x' => 200, 'y' => 200, 'radius' => 100, 'filled' => 1});
25             $fb->polygon({'coordinates' => [20,20, 53,3, 233,620], 'pixel_size' => 5});
26             $fb->box({'x' => 95, 'y' => 100, 'xx' => 400, 'yy' => 600, 'filled' => 1});
27             # ... and many many more
28              
29             $fb->cls('ON'); # Clear screen and turn on the console cursor
30              
31             Methods requiring parameters require a hash (or anonymous hash) reference passed to the method. All parameters have easy to understand english names, all lower case, to understand exactly what the method is doing.
32              
33             =head1 DESCRIPTION
34              
35             A (mostly) Perl graphics library for exclusive use in a Linux/FreeBSD/Unix console framebuffer environment. It is written for simplicity, without the need for complex API's and drivers with "surfaces" and such.
36              
37             Back in the old days, computers drew graphics this way, and it was simple and easy to do. I was writing a console based media playing program, and was not satisfied with the limited abilities offered by the nCurses library, and I did not want the overhead of the X-Windows environment to get in the way. My intention was to create a mobile media server. In case you are wondering, that project has been quite successful, and I am still making improvements to it. I may even include it in the "examples" directory on future versions.
38              
39             There are places where Perl just won't cut it. So I use the Imager library to take up the slack, or my own C code. Imager is just used to load images,, save images, merge, rotate, and draw TrueType/Type1 text. I am also incorporating compiled C to further assist with speed. That is being implemented step by step, but "acceleration" will always be optional, and pure Perl routines always available for those systems without a C compiler or "Inline:C" available.
40              
41             I cannot guarantee this will work on your video card, but I have successfully tested it on NVidia GeForce, AMD Radeon, Matrox, Raspberry PI, Odroid XU3/XU4, and VirtualBox displays. However, you MUST remember, your video driver MUST be framebuffer based. The proprietary Nvidia and AMD drivers (with DRM) will NOT work with this module. You must use the open source video drivers, such as Nouveau, to be able to use this library (with output to see). Also, it is not going to work from within X-Windows, so don't even try it, it will either crash X, or make a mess on the screen. This is a console only graphics library.
42              
43             NOTE:
44              
45             If a framebuffer is not available, the module will go into emulation mode and open a pseudo-screen in the object's hash variable 'SCREEN'
46              
47             You can write this to a file, whatever. It defaults to a 640x480x32 RGB graphics 'buffer'. However, you can change that by passing parameters to the 'new' method.
48              
49             You will not be able to see the output directly when in emulation mode. I mainly created this mode so that you could install this module (on systems without a framebuffer) and test code you may be writing to be used on other devices that have accessible framebuffer devices. Nevertheless, I have learned that people use emulation mode as an offscreen drawing surface, and blit from one to the other. Which is pretty clever.
50              
51             Make sure you have read/write access to the framebuffer device. Usually this just means adding your account to the "video" group (make sure you log out and log in again after doing that). Alternately, you can just run your script as root. Although I don't recommend it.
52              
53             =head1 INSTALLATION
54              
55             Read the file "installing/INSTALL" and follow its instructions.
56              
57             =back
58              
59             When you install this module, please do it within a console, not a console window in X-Windows, but the actual Linux/FreeBSD console outside of X-Windows.
60              
61             If you are in X-Windows, and don't know how to get to a console, then just hit CTRL-ALT-F1 (actually CTRL-ALT-F1 through CTRL-ALT-F6 works) and it should show you a console. ALT-F7 or ALT-F8 will get you back to X-Windows.
62              
63             =head1 OPERATIONAL THEORY
64              
65             How many Perl modules actually tell you how they work? Well, I will tell you how this one works.
66              
67             The framebuffer is simply a special file that is mapped to the screen. How the driver does this can be different. Some may actually directly map the display memory to this file, and some install a second copy of the display to normal memory and copy it to the display on every vertical blank, usually with a fast DMA transfer.
68              
69             This module maps that file to a string, and that ends up making the string exactly the same size as the physical display. Plotting is simply a matter of calculating where in the string that pixel is and modifying it, via "substr" (never using "=" directly). It's that simple.
70              
71             Drawing lines etc. requires some algorithmic magic though, but they all call the plot routine to do their eventual magic.
72              
73             Originally everything was done in Perl, and the module's speed was mostly acceptable, unless you had a really slow system. It still can run in pure Perl, if you turn off the acceleration feature, although I do not recommend it, if you want speed.
74              
75             =head1 SPECIAL VARIABLES
76              
77             The following are hash keys to the main object variable. For example, if you use the variable $fb as the object variable, then the following are $fb->{VARIABLE_NAME}
78              
79             =over 4
80              
81             =item B
82              
83             List of system fonts
84              
85             Contains a hash of every font found in the system in the format:
86              
87             =back
88              
89             =over 6
90              
91             # 'FaceName' => {
92             # 'path' => 'Path To Font',
93             # 'font' => 'File Name of Font'
94             # },
95             # ...
96              
97             =back
98              
99             =over 4
100              
101             =item B
102              
103             If your installation of Imager has TrueType font capability, then this will be 1
104              
105             =item B
106              
107             If your installation of Imager has Adobe Type 1 font capability, then this will be 1
108              
109             =item B
110              
111             If your installation of Imager has the FreeType2 library rendering capability, then this will be 1
112              
113             =item B
114              
115             An anonymous array of supported image file types.
116              
117             =item B
118              
119             An anomyous array of hatch names for hatch fills.
120              
121             This is also exported as @HATCHES
122              
123             =item B
124              
125             The top left-hand corner X location of the clipping region
126              
127             =item B
128              
129             The top left-hand corner Y location of the clipping region
130              
131             =item B
132              
133             The bottom right-hand corner X location of the clipping region
134              
135             =item B
136              
137             The bottom right-hand corner Y location of the clipping region.
138              
139             =item B
140              
141             If this is true, then the clipping region is smaller than the full screen
142              
143             If false, then the clipping region is the screen dimensions.
144              
145             =item B
146              
147             The current drawing mode. This is a numeric value corresponding to the constants described in the method 'draw_mode'
148              
149             =item B
150              
151             The current foreground color encoded as a string.
152              
153             =item B
154              
155             The current background color encoded as a string.
156              
157             =item B
158              
159             Indicates if C code or hardware acceleration is being used.
160              
161             =back
162              
163             =over 6
164              
165             =item B
166              
167             0 = Perl code only
168             1 = Some functions accelerated by compiled code (Default)
169             2 = All of #1 plus additional functions accelerated by hardware (currently not supported)
170              
171             =back
172              
173             Many of the parameters you pass to the "new" method are also special variables.
174              
175             =cut
176              
177 1     1   81205 use strict;
  1         3  
  1         41  
178 1     1   5 no strict 'vars'; # We have to map a variable as the screen. So strict is going to whine about what we do with it.
  1         2  
  1         30  
179              
180 1     1   6 no warnings; # We have to be as quiet as possible
  1         2  
  1         189  
181              
182             =head1 CONSTANTS
183              
184             The following constants can be used in the various methods. Each method example will have the possible constants to use for that method.
185              
186             The value of the constant is in parenthesis:
187              
188             B (value)
189              
190             Boolean constants
191              
192             =over 8
193              
194             =item B ( 1 )
195             =item B ( 0 )
196              
197             =back
198              
199             Draw mode constants
200              
201             =over 8
202              
203             =item B ( 0 )
204             =item B ( 1 )
205             =item B ( 2 )
206             =item B ( 3 )
207             =item B ( 4 )
208             =item B ( 5 )
209             =item B ( 6 )
210             =item B ( 7 )
211             =item B ( 8 )
212             =item B ( 9 )
213             =item B ( 10 )
214              
215             =back
216              
217             Draw Arc constants
218              
219             =over 8
220              
221             =item B ( 0 )
222             =item B ( 1 )
223             =item B ( 2 )
224              
225             =back
226              
227             Virtual framebuffer color mode constants
228              
229             =over 8
230              
231             =item B ( 0 )
232             =item B ( 1 )
233             =item B ( 2 )
234             =item B ( 3 )
235             =item B ( 4 )
236             =item B ( 5 )
237              
238             =back
239              
240             Text rendering centering constants
241              
242             =over 8
243              
244             =item B ( 0 )
245             =item B ( 1 )
246             =item B ( 2 )
247             =item B ( 3 )
248              
249             =back
250              
251             Acceleration method constants
252              
253             =over 8
254              
255             =item B ( 0 )
256             =item B ( 1 )
257             =item B ( 2 )
258              
259             =back
260              
261             =cut
262              
263             use constant {
264 1         633 TRUE => 1,
265             FALSE => 0,
266              
267             NORMAL_MODE => 0, # Constants for DRAW_MODE
268             XOR_MODE => 1,
269             OR_MODE => 2,
270             AND_MODE => 3,
271             MASK_MODE => 4,
272             UNMASK_MODE => 5,
273             ALPHA_MODE => 6,
274             ADD_MODE => 7,
275             SUBTRACT_MODE => 8,
276             MULTIPLY_MODE => 9,
277             DIVIDE_MODE => 10,
278              
279             ARC => 0, # Constants for "draw_arc" method
280             PIE => 1,
281             POLY_ARC => 2,
282              
283             RGB => 0, # Constants for color mapping
284             RBG => 1,
285             BGR => 2,
286             BRG => 3,
287             GBR => 4,
288             GRB => 5,
289              
290             CENTER_NONE => 0, # Constants for centering
291             CENTER_X => 1,
292             CENTER_Y => 2,
293             CENTER_XY => 3,
294             CENTRE_NONE => 0, # Constants for centering (for British and Canadian folks)
295             CENTRE_X => 1,
296             CENTRE_Y => 2,
297             CENTRE_XY => 3,
298              
299             PERL => 0,
300             SOFTWARE => 1,
301             HARDWARE => 2,
302              
303             ## Set up the Framebuffer driver "constants" defaults
304             # Commands
305             FBIOGET_VSCREENINFO => 0x4600, # These come from "fb.h" in the kernel source
306             FBIOPUT_VSCREENINFO => 0x4601,
307             FBIOGET_FSCREENINFO => 0x4602,
308             FBIOGETCMAP => 0x4604,
309             FBIOPUTCMAP => 0x4605,
310             FBIOPAN_DISPLAY => 0x4606,
311             FBIO_CURSOR => 0x4608,
312             FBIOGET_CON2FBMAP => 0x460F,
313             FBIOPUT_CON2FBMAP => 0x4610,
314             FBIOBLANK => 0x4611,
315             FBIOGET_VBLANK => 0x4612,
316             FBIOGET_GLYPH => 0x4615,
317             FBIOGET_HWCINFO => 0x4616,
318             FBIOPUT_MODEINFO => 0x4617,
319             FBIOGET_DISPINFO => 0x4618,
320             FBIO_WAITFORVSYNC => 0x4620,
321             VT_GETSTATE => 0x5603,
322             KDSETMODE => 0x4B3A,
323             KD_GRAPHICS => 1,
324             KD_TEXT => 0,
325              
326             # FLAGS
327             FBINFO_HWACCEL_NONE => 0x0000, # These come from "fb.h" in the kernel source
328             FBINFO_HWACCEL_COPYAREA => 0x0100,
329             FBINFO_HWACCEL_FILLRECT => 0x0200,
330             FBINFO_HWACCEL_IMAGEBLIT => 0x0400,
331             FBINFO_HWACCEL_ROTATE => 0x0800,
332             FBINFO_HWACCEL_XPAN => 0x1000,
333             FBINFO_HWACCEL_YPAN => 0x2000,
334             FBINFO_HWACCEL_YWRAP => 0x4000,
335 1     1   8 };
  1         2  
336              
337             ### THREADS ###
338 1     1   856 use if ($Config{'useithreads'}), 'threads', 'yield', 'stringify', 'stack_size' => 131076, 'exit' => 'threads_only';
  1         17  
  1         7  
339 1     1   57 use if ($Config{'useithreads'}), 'threads::shared';
  1         2  
  1         6  
340              
341 1     1   595 use POSIX ();
  1         7371  
  1         40  
342 1     1   11 use POSIX qw(modf);
  1         3  
  1         7  
343 1     1   2577 use Time::HiRes qw(sleep time); # The time accuracy has to be milliseconds on many routines
  1         1628  
  1         6  
344 1     1   846 use Math::Trig ':pi'; # Usually only PI is used
  1         17685  
  1         193  
345 1     1   679 use Math::Bezier; # Bezier curve calculations done here.
  1         917  
  1         36  
346 1     1   506 use Math::Gradient qw( gradient array_gradient multi_gradient ); # Awesome gradient calculation module
  1         1096  
  1         98  
347 1     1   10 use List::Util qw(min max); # min and max are very handy!
  1         2  
  1         92  
348 1     1   726 use File::Map ':map'; # Absolutely necessary to map the screen to a string.
  1         7317  
  1         73  
349 1     1   1262 use Imager; # This is used for TrueType font printing, image loading.
  1         50938  
  1         10  
350 1     1   780 use Imager::Matrix2d;
  1         2583  
  1         75  
351 1     1   737 use Imager::Fill; # For hatch fills
  1         1405  
  1         41  
352 1     1   588 use Imager::Fountain; #
  1         2720  
  1         46  
353 1     1   727 use Imager::Font::Wrap;
  1         1548  
  1         46  
354 1     1   665 use Graphics::Framebuffer::Mouse; # The mouse handler
  1         5  
  1         86  
355 1     1   581 use Graphics::Framebuffer::Splash; # The splash code is here
  1         4  
  1         202  
356              
357             Imager->preload; # The Imager documentation says to do this, but doesn't give much of an explanation why. However, I assume it is to initialize global variables ahead of time.
358              
359             ## This is for debugging, and should normally be commented out.
360             # use Data::Dumper::Simple;$Data::Dumper::Sortkeys=1;$Data::Dumper::Purity=1;
361              
362             BEGIN {
363 1     1   16 require Exporter;
364              
365             # set the version for version checking
366 1         2 our $VERSION = '6.49';
367 1         35 our @ISA = qw(Exporter);
368 1         7 our @EXPORT_OK = qw(
369             FBIOGET_VSCREENINFO
370             FBIOPUT_VSCREENINFO
371             FBIOGET_FSCREENINFO
372             FBIOGETCMAP
373             FBIOPUTCMAP
374             FBIOPAN_DISPLAY
375             FBIO_CURSOR
376             FBIOGET_CON2FBMAP
377             FBIOPUT_CON2FBMAP
378             FBIOBLANK
379             FBIOGET_VBLANK
380             FBIOGET_GLYPH
381             FBIOGET_HWCINFO
382             FBIOPUT_MODEINFO
383             FBIOGET_DISPINFO
384             FBIO_WAITFORVSYNC
385             VT_GETSTATE
386             FBINFO_HWACCEL_NONE
387             FBINFO_HWACCEL_COPYAREA
388             FBINFO_HWACCEL_FILLRECT
389             FBINFO_HWACCEL_IMAGEBLIT
390             FBINFO_HWACCEL_ROTATE
391             FBINFO_HWACCEL_XPAN
392             FBINFO_HWACCEL_YPAN
393             FBINFO_HWACCEL_YWRAP
394             $VERSION
395             );
396 1         813 our @EXPORT = qw(
397             TRUE
398             FALSE
399             NORMAL_MODE
400             XOR_MODE
401             OR_MODE
402             AND_MODE
403             MASK_MODE
404             UNMASK_MODE
405             ALPHA_MODE
406             ADD_MODE
407             SUBTRACT_MODE
408             MULTIPLY_MODE
409             DIVIDE_MODE
410             ARC
411             PIE
412             POLY_ARC
413             RGB
414             RBG
415             BGR
416             BRG
417             GBR
418             GRB
419             CENTER_NONE
420             CENTER_X
421             CENTER_Y
422             CENTER_XY
423             CENTRE_NONE
424             CENTRE_X
425             CENTRE_Y
426             CENTRE_XY
427             PERL
428             SOFTWARE
429             HARDWARE
430             @HATCHES
431             @COLORORDER
432             );
433             }
434              
435             sub DESTROY { # Always clean up after yourself before exiting
436 2     2   32 my $self = shift;
437 2         28 $self->text_mode();
438 2         39 $self->_screen_close();
439 2 50       737 _reset() if ($self->{'RESET'}); # Exit by calling 'reset' first
440             }
441              
442             # use Inline 'info', 'noclean', 'noisy'; # Only needed for debugging
443              
444 1     1   892 use Inline C => <<'C_CODE','name' => 'Graphics::Framebuffer', 'VERSION' => $VERSION;
  1         38621  
  1         11  
445             /* Copyright 2018-2021 Richard Kelsch, All Rights Reserved
446             See the Perl documentation for Graphics::Framebuffer for licensing information.
447              
448             Version: 6.48
449              
450             You may wonder why the stack is so heavily used when the global structures
451             have the needed values. Well, the module can emulate another graphics mode
452             that may not be the one being displayed. This means using the two structures
453             would break functionality. Therefore, the data from Perl is passed along.
454             */
455              
456             #include
457             #include
458             #include
459             #include
460             #include
461             #include
462             #include
463             #include
464             #include
465              
466             #define NORMAL_MODE 0
467             #define XOR_MODE 1
468             #define OR_MODE 2
469             #define AND_MODE 3
470             #define MASK_MODE 4
471             #define UNMASK_MODE 5
472             #define ALPHA_MODE 6
473             #define ADD_MODE 7
474             #define SUBTRACT_MODE 8
475             #define MULTIPLY_MODE 9
476             #define DIVIDE_MODE 10
477              
478             #define RGB 0
479             #define RBG 1
480             #define BGR 2
481             #define BRG 3
482             #define GBR 4
483             #define GRB 5
484              
485             #define integer_(X) ((int)(X))
486             #define round_(X) ((int)(((double)(X))+0.5))
487             #define decimal_(X) (((double)(X))-(double)integer_(X))
488             #define rdecimal_(X) (1.0-decimal_(X))
489             #define swap_(a, b) do { __typeof__(a) tmp; tmp = a; a = b; b = tmp; } while(0)
490              
491             /* Global Structures */
492             struct fb_var_screeninfo vinfo;
493             struct fb_fix_screeninfo finfo;
494              
495             // This gets the framebuffer info and populates the above structures, then sends them to Perl
496             void c_get_screen_info(char *fb_file) {
497             int fbfd = open(fb_file,O_RDWR);
498             ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
499             ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
500             close(fbfd);
501              
502             // This monstrosity pushes the needed values on Perl's stack, like "return" does.
503              
504             Inline_Stack_Vars;
505             Inline_Stack_Reset;
506              
507             Inline_Stack_Push(sv_2mortal(newSVpvn(finfo.id,16)));
508             Inline_Stack_Push(sv_2mortal(newSVnv(finfo.smem_start)));
509             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.smem_len)));
510             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.type)));
511             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.type_aux)));
512             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.visual)));
513             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.xpanstep)));
514             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.ypanstep)));
515             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.ywrapstep)));
516             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.line_length)));
517             Inline_Stack_Push(sv_2mortal(newSVnv(finfo.mmio_start)));
518             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.mmio_len)));
519             Inline_Stack_Push(sv_2mortal(newSVuv(finfo.accel)));
520              
521             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.xres)));
522             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.yres)));
523             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.xres_virtual)));
524             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.yres_virtual)));
525             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.xoffset)));
526             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.yoffset)));
527             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.bits_per_pixel)));
528             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.grayscale)));
529             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.red.offset)));
530             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.red.length)));
531             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.red.msb_right)));
532             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.green.offset)));
533             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.green.length)));
534             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.green.msb_right)));
535             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.blue.offset)));
536             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.blue.length)));
537             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.blue.msb_right)));
538             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.transp.offset)));
539             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.transp.length)));
540             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.transp.msb_right)));
541             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.nonstd)));
542             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.activate)));
543             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.height)));
544             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.accel_flags)));
545             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.pixclock)));
546             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.left_margin)));
547             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.right_margin)));
548             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.upper_margin)));
549             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.lower_margin)));
550             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.hsync_len)));
551             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.vsync_len)));
552             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.sync)));
553             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.vmode)));
554             Inline_Stack_Push(sv_2mortal(newSVnv(vinfo.rotate)));
555              
556             Inline_Stack_Done;
557             }
558              
559             // Sets the framebuffer to text mode, which enables the cursor
560             void c_text_mode(char *tty_file)
561             {
562             int tty_fd = open(tty_file,O_RDWR);
563             ioctl(tty_fd,KDSETMODE,KD_TEXT);
564             close(tty_fd);
565             }
566              
567             // Sets the framebuffer to graphics mode, which disables the cursor
568             void c_graphics_mode(char *tty_file)
569             {
570             int tty_fd = open(tty_file,O_RDWR);
571             ioctl(tty_fd,KDSETMODE,KD_GRAPHICS);
572             close(tty_fd);
573             }
574              
575              
576             /* The other routines call this. It handles all draw modes
577             *
578             * Normally I would add code to properly place the RGB values according to
579             * color order, but in reality, that can be done solely when the color value
580             * itself is defined, so the colors are in the correct order before even
581             * arriving at this routine.
582             */
583             void c_plot(
584             char *framebuffer,
585             short x, short y,
586             short x_clip, short y_clip, short xx_clip, short yy_clip,
587             unsigned int color,
588             unsigned int bcolor,
589             unsigned char alpha,
590             unsigned char draw_mode,
591             unsigned char bytes_per_pixel,
592             unsigned char bits_per_pixel,
593             unsigned int bytes_per_line,
594             short xoffset, short yoffset)
595             {
596             if (x >= x_clip && x <= xx_clip && y >= y_clip && y <= yy_clip) { // Make sure the pixel is within the clipped area
597             x += xoffset;
598             y += yoffset;
599             unsigned int index = (x * bytes_per_pixel) + (y * bytes_per_line);
600             switch(draw_mode) {
601             case NORMAL_MODE :
602             switch(bits_per_pixel) {
603             case 32 :
604             {
605             *((unsigned int*)(framebuffer + index)) = color; // 32 bit drawing can send a long word in one operation. Which is why it is the fastest.
606             }
607             break;
608             case 24 :
609             {
610             *(framebuffer + index) = color & 255; // 24 Bit requites one byte at a time. Not as efficient as 32 bit.
611             *(framebuffer + index + 1) = (color >> 8) & 255;
612             *(framebuffer + index + 2) = (color >> 16) & 255;
613             }
614             break;
615             case 16 :
616             {
617             *((unsigned short*)(framebuffer + index)) = (short) color; // 16 bit can send a word at a time, the second most efficient method.
618             }
619             break;
620             }
621             break;
622             case XOR_MODE :
623             switch(bits_per_pixel) {
624             case 32 :
625             {
626             *((unsigned int*)(framebuffer + index)) ^= color;
627             }
628             break;
629             case 24 :
630             {
631             *(framebuffer + index) ^= color & 255;
632             *(framebuffer + index + 1) ^= (color >> 8) & 255;
633             *(framebuffer + index + 2) ^= (color >> 16) & 255;
634             }
635             break;
636             case 16 :
637             {
638             *((unsigned short*)(framebuffer + index)) ^= (short) color;
639             }
640             break;
641             }
642             break;
643             case OR_MODE :
644             switch(bits_per_pixel) {
645             case 32 :
646             {
647             *((unsigned int*)(framebuffer + index)) |= color;
648             }
649             break;
650             case 24 :
651             {
652             *(framebuffer + index) |= color & 255;
653             *(framebuffer + index + 1) |= (color >> 8) & 255;
654             *(framebuffer + index + 2) |= (color >> 16) & 255;
655             }
656             break;
657             case 16 :
658             {
659             *((unsigned short*)(framebuffer + index)) |= (short) color;
660             }
661             break;
662             }
663             break;
664             case AND_MODE :
665             switch(bits_per_pixel) {
666             case 32 :
667             {
668             *((unsigned int*)(framebuffer + index)) &= color;
669             }
670             break;
671             case 24 :
672             {
673             *(framebuffer + index) &= color & 255;
674             *(framebuffer + index + 1) &= (color >> 8) & 255;
675             *(framebuffer + index + 2) &= (color >> 16) & 255;
676             }
677             break;
678             case 16 :
679             {
680             *((unsigned short*)(framebuffer + index)) &= (short) color;
681             }
682             break;
683             }
684             break;
685             case MASK_MODE :
686             switch(bits_per_pixel) {
687             case 32 :
688             {
689             if ((*((unsigned int*)(framebuffer + index )) & 0xFFFFFF00) != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
690             *((unsigned int*)(framebuffer + index )) = color;
691             }
692             }
693             break;
694             case 24 :
695             {
696             if ((*((unsigned int*)(framebuffer + index )) & 0xFFFFFF00) != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
697             *(framebuffer + index ) = color & 255;
698             *(framebuffer + index + 1) = (color >> 8) & 255;
699             *(framebuffer + index + 2) = (color >> 16) & 255;
700             }
701             }
702             break;
703             case 16 :
704             {
705             if (*((unsigned short*)(framebuffer + index)) != (bcolor & 0xFFFF)) {
706             *((unsigned short*)(framebuffer + index )) = color;
707             }
708             }
709             break;
710             }
711             break;
712             case UNMASK_MODE :
713             switch(bits_per_pixel) {
714             case 32 :
715             {
716             if ((*((unsigned int*)(framebuffer + index )) & 0xFFFFFF00) == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
717             *((unsigned int*)(framebuffer + index )) = color;
718             }
719             }
720             break;
721             case 24 :
722             {
723             if ((*((unsigned int*)(framebuffer + index )) & 0xFFFFFF00) == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
724             *(framebuffer + index ) = color & 255;
725             *(framebuffer + index + 1) = (color >> 8) & 255;
726             *(framebuffer + index + 2) = (color >> 16) & 255;
727             }
728             }
729             break;
730             case 16 :
731             {
732             if (*((unsigned short*)(framebuffer + index)) == (bcolor & 0xFFFF)) {
733             *((unsigned short*)(framebuffer + index )) = color;
734             }
735             }
736             break;
737             }
738             break;
739             case ALPHA_MODE :
740             switch(bits_per_pixel) {
741             case 32 :
742             {
743             unsigned int fb_rgb = *((unsigned int*)(framebuffer + index));
744             unsigned char fb_r = fb_rgb & 255;
745             unsigned char fb_g = (fb_rgb >> 8) & 255;
746             unsigned char fb_b = (fb_rgb >> 16) & 255;
747             unsigned char R = color & 255;
748             unsigned char G = (color >> 8) & 255;
749             unsigned char B = (color >> 16) & 255;
750             unsigned char A = (color >> 24) & 255;
751             unsigned char invA = (255 - A);
752              
753             fb_r = ((R * A) + (fb_r * invA)) >> 8;
754             fb_g = ((G * A) + (fb_g * invA)) >> 8;
755             fb_b = ((B * A) + (fb_b * invA)) >> 8;
756              
757             *((unsigned int*)(framebuffer + index)) = fb_r | (fb_g << 8) | (fb_b << 16) | (A << 24);
758             }
759             break;
760             case 24 :
761             {
762             unsigned char fb_r = *(framebuffer + index);
763             unsigned char fb_g = *(framebuffer + index + 1);
764             unsigned char fb_b = *(framebuffer + index + 2);
765             unsigned char invA = (255 - alpha);
766             unsigned char R = color & 255;
767             unsigned char G = (color >> 8) & 255;
768             unsigned char B = (color >> 16) & 255;
769              
770             fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
771             fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
772             fb_b = ((B * alpha) + (fb_b * invA)) >> 8;
773              
774             *(framebuffer + index) = fb_r;
775             *(framebuffer + index + 1) = fb_g;
776             *(framebuffer + index + 2) = fb_b;
777             }
778             break;
779             case 16 :
780             {
781             unsigned short rgb565 = *((unsigned short*)(framebuffer + index));
782             unsigned short fb_r = rgb565 & 31;
783             unsigned short fb_g = (rgb565 >> 5) & 63;
784             unsigned short fb_b = (rgb565 >> 11) & 31;
785             unsigned short R = color & 31;
786             unsigned short G = (color >> 5) & 63;
787             unsigned short B = (color >> 11) & 31;
788             unsigned char invA = (255 - alpha);
789             fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
790             fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
791             fb_b = ((B * alpha) + (fb_b * invA)) >> 8;
792             rgb565 = (fb_b << 11) | (fb_g << 5) | fb_r;
793             *((unsigned short*)(framebuffer + index)) = rgb565;
794             }
795             break;
796             }
797             break;
798             case ADD_MODE :
799             switch(bits_per_pixel) {
800             case 32 :
801             {
802             *((unsigned int*)(framebuffer + index)) += color;
803             }
804             break;
805             case 24 :
806             {
807             *(framebuffer + index) += color & 255;
808             *(framebuffer + index + 1) += (color >> 8) & 255;
809             *(framebuffer + index + 2) += (color >> 16) & 255;
810             }
811             break;
812             case 16 :
813             {
814             *((unsigned short*)(framebuffer + index)) += (short) color;
815             }
816             break;
817             }
818             break;
819             case SUBTRACT_MODE :
820             switch(bits_per_pixel) {
821             case 32 :
822             {
823             *((unsigned int*)(framebuffer + index)) -= color;
824             }
825             break;
826             case 24 :
827             {
828             *(framebuffer + index) -= color & 255;
829             *(framebuffer + index + 1) -= (color >> 8) & 255;
830             *(framebuffer + index + 2) -= (color >> 16) & 255;
831             }
832             break;
833             case 16 :
834             {
835             *((unsigned short*)(framebuffer + index)) -= (short) color;
836             }
837             break;
838             }
839             break;
840             case MULTIPLY_MODE :
841             switch(bits_per_pixel) {
842             case 32 :
843             {
844             *((unsigned int*)(framebuffer + index)) *= color;
845             }
846             break;
847             case 24 :
848             {
849             *(framebuffer + index) *= color & 255;
850             *(framebuffer + index + 1) *= (color >> 8) & 255;
851             *(framebuffer + index + 2) *= (color >> 16) & 255;
852             }
853             break;
854             case 16 :
855             {
856             *((unsigned short*)(framebuffer + index)) *= (short) color;
857             }
858             break;
859             }
860             break;
861             case DIVIDE_MODE :
862             switch(bits_per_pixel) {
863             case 32 :
864             {
865             *((unsigned int*)(framebuffer + index)) /= color;
866             }
867             break;
868             case 24 :
869             {
870             *(framebuffer + index) /= color & 255;
871             *(framebuffer + index + 1) /= (color >> 8) & 255;
872             *(framebuffer + index + 2) /= (color >> 16) & 255;
873             }
874             break;
875             case 16 :
876             {
877             *((unsigned short*)(framebuffer + index)) /= (short) color;
878             }
879             break;
880             }
881             break;
882             }
883             }
884             }
885              
886             // Draws a line
887             void c_line(
888             char *framebuffer,
889             short x1, short y1, short x2, short y2,
890             short x_clip, short y_clip, short xx_clip, short yy_clip,
891             unsigned int color,
892             unsigned int bcolor,
893             unsigned char alpha,
894             unsigned char draw_mode,
895             unsigned char bytes_per_pixel,
896             unsigned char bits_per_pixel,
897             unsigned int bytes_per_line,
898             short xoffset, short yoffset)
899             {
900             short shortLen = y2 - y1;
901             short longLen = x2 - x1;
902             int yLonger = false;
903              
904             if (abs(shortLen) > abs(longLen)) {
905             short swap = shortLen;
906             shortLen = longLen;
907             longLen = swap;
908             yLonger = true;
909             }
910             int decInc;
911             if (longLen == 0) {
912             decInc = 0;
913             } else {
914             decInc = (shortLen << 16) / longLen;
915             }
916             int count;
917             if (yLonger) {
918             if (longLen > 0) {
919             longLen += y1;
920             for (count = 0x8000 + (x1 << 16); y1 <= longLen; ++y1) {
921             c_plot(framebuffer, count >> 16, y1, x_clip, y_clip, xx_clip, yy_clip, color, bcolor, alpha, draw_mode, bytes_per_pixel, bits_per_pixel, bytes_per_line, xoffset, yoffset);
922             count += decInc;
923             }
924             return;
925             }
926             longLen += y1;
927             for (count = 0x8000 + (x1 << 16); y1 >= longLen; --y1) {
928             c_plot(framebuffer, count >> 16, y1, x_clip, y_clip, xx_clip, yy_clip, color, bcolor, alpha, draw_mode, bytes_per_pixel, bits_per_pixel, bytes_per_line, xoffset, yoffset);
929             count -= decInc;
930             }
931             return;
932             }
933             if (longLen > 0) {
934             longLen += x1;
935             for (count = 0x8000 + (y1 << 16); x1 <= longLen; ++x1) {
936             c_plot(framebuffer, x1, count >> 16, x_clip, y_clip, xx_clip, yy_clip, color, bcolor, alpha, draw_mode, bytes_per_pixel, bits_per_pixel, bytes_per_line, xoffset, yoffset);
937             count += decInc;
938             }
939             return;
940             }
941             longLen += x1;
942             for (count = 0x8000 + (y1 << 16); x1 >= longLen; --x1) {
943             c_plot(framebuffer, x1, count >> 16, x_clip, y_clip, xx_clip, yy_clip, color, bcolor, alpha, draw_mode, bytes_per_pixel, bits_per_pixel, bytes_per_line, xoffset, yoffset);
944             count -= decInc;
945             }
946             }
947              
948             // Reads in rectangular screen data as a string to a previously allocated buffer
949             void c_blit_read(
950             char *framebuffer,
951             short screen_width, short screen_height,
952             unsigned int bytes_per_line,
953             short xoffset, short yoffset,
954             char *blit_data,
955             short x, short y, short w, short h,
956             unsigned char bytes_per_pixel,
957             unsigned char draw_mode,
958             unsigned char alpha,
959             unsigned int bcolor,
960             short x_clip, short y_clip, short xx_clip, short yy_clip)
961             {
962             short fb_x = xoffset + x;
963             short fb_y = yoffset + y;
964             short xx = x + w;
965             short yy = y + h;
966             short horizontal;
967             short vertical;
968             unsigned int bline = w * bytes_per_pixel;
969              
970             for (vertical = 0; vertical < h; vertical++) {
971             unsigned int vbl = vertical * bline;
972             unsigned short yv = fb_y + vertical;
973             unsigned int yvbl = yv * bytes_per_line;
974             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
975             for (horizontal = 0; horizontal < w; horizontal++) {
976             unsigned short xh = fb_x + horizontal;
977             unsigned int xhbp = xh * bytes_per_pixel;
978             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
979             unsigned int hzpixel = horizontal * bytes_per_pixel;
980             unsigned int vhz = vbl + hzpixel;
981             unsigned int yvhz = yvbl + hzpixel;
982             unsigned int xhbp_yvbl = xhbp + yvbl;
983             if (bytes_per_pixel == 4) {
984             *((unsigned int*)(blit_data + vhz)) = *((unsigned int*)(framebuffer + xhbp_yvbl));
985             } else if (bytes_per_pixel == 3) {
986             *(blit_data + vhz ) = *(framebuffer + xhbp_yvbl );
987             *(blit_data + vhz + 1) = *(framebuffer + xhbp_yvbl + 1);
988             *(blit_data + vhz + 2) = *(framebuffer + xhbp_yvbl + 2);
989             } else {
990             *((unsigned short*)(blit_data + vhz )) = *((unsigned short*)(framebuffer + xhbp_yvbl ));
991             }
992             }
993             }
994             }
995             }
996             }
997              
998             // Blits a rectangle of graphics to the screen using the specified draw mode
999             void c_blit_write(
1000             char *framebuffer,
1001             short screen_width, short screen_height,
1002             unsigned int bytes_per_line,
1003             short xoffset, short yoffset,
1004             char *blit_data,
1005             short x, short y, short w, short h,
1006             unsigned char bytes_per_pixel,
1007             unsigned char draw_mode,
1008             unsigned char alpha,
1009             unsigned int bcolor,
1010             short x_clip, short y_clip, short xx_clip, short yy_clip)
1011             {
1012             short fb_x = xoffset + x;
1013             short fb_y = yoffset + y;
1014             short xx = x + w;
1015             short yy = y + h;
1016             short horizontal;
1017             short vertical;
1018             unsigned int bline = w * bytes_per_pixel;
1019              
1020             // Fastest is unclipped normal mode
1021             if (draw_mode == NORMAL_MODE && x >= x_clip && xx <= xx_clip && y >= y_clip && yy <= yy_clip) {
1022             unsigned char *source = blit_data;
1023             unsigned char *dest = &framebuffer[(fb_y * bytes_per_line) + (fb_x * bytes_per_pixel)];
1024             for (vertical = 0; vertical < h; vertical++) {
1025             memcpy(dest, source, bline);
1026             source += bline;
1027             dest += bytes_per_line;
1028             }
1029             } else {
1030             switch(draw_mode) {
1031             case NORMAL_MODE :
1032             switch(bytes_per_pixel) {
1033             case 4 :
1034             for (vertical = 0; vertical < h; vertical++) {
1035             unsigned int vbl = vertical * bline;
1036             unsigned short yv = fb_y + vertical;
1037             unsigned int yvbl = yv * bytes_per_line;
1038             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1039             for (horizontal = 0; horizontal < w; horizontal++) {
1040             unsigned short xh = fb_x + horizontal;
1041             unsigned int xhbp = xh * bytes_per_pixel;
1042             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1043             unsigned int hzpixel = horizontal * bytes_per_pixel;
1044             unsigned int vhz = vbl + hzpixel;
1045             unsigned int yvhz = yvbl + hzpixel;
1046             unsigned int xhbp_yvbl = xhbp + yvbl;
1047             *((unsigned int*)(framebuffer + xhbp_yvbl)) = *((unsigned int*)(blit_data + vhz));
1048             }
1049             }
1050             }
1051             }
1052             break;
1053             case 3 :
1054             for (vertical = 0; vertical < h; vertical++) {
1055             unsigned int vbl = vertical * bline;
1056             unsigned short yv = fb_y + vertical;
1057             unsigned int yvbl = yv * bytes_per_line;
1058             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1059             for (horizontal = 0; horizontal < w; horizontal++) {
1060             unsigned short xh = fb_x + horizontal;
1061             unsigned int xhbp = xh * bytes_per_pixel;
1062             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1063             unsigned int hzpixel = horizontal * bytes_per_pixel;
1064             unsigned int vhz = vbl + hzpixel;
1065             unsigned int yvhz = yvbl + hzpixel;
1066             unsigned int xhbp_yvbl = xhbp + yvbl;
1067             *(framebuffer + xhbp_yvbl ) = *(blit_data + vhz );
1068             *(framebuffer + xhbp_yvbl + 1) = *(blit_data + vhz + 1);
1069             *(framebuffer + xhbp_yvbl + 2) = *(blit_data + vhz + 2);
1070             }
1071             }
1072             }
1073             }
1074             break;
1075             case 2 :
1076             for (vertical = 0; vertical < h; vertical++) {
1077             unsigned int vbl = vertical * bline;
1078             unsigned short yv = fb_y + vertical;
1079             unsigned int yvbl = yv * bytes_per_line;
1080             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1081             for (horizontal = 0; horizontal < w; horizontal++) {
1082             unsigned short xh = fb_x + horizontal;
1083             unsigned int xhbp = xh * bytes_per_pixel;
1084             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1085             unsigned int hzpixel = horizontal * bytes_per_pixel;
1086             unsigned int vhz = vbl + hzpixel;
1087             unsigned int yvhz = yvbl + hzpixel;
1088             unsigned int xhbp_yvbl = xhbp + yvbl;
1089             *((unsigned short*)(framebuffer + xhbp_yvbl )) = *((unsigned short*)(blit_data + vhz ));
1090             }
1091             }
1092             }
1093             }
1094             break;
1095             }
1096             break;
1097             case XOR_MODE :
1098             switch(bytes_per_pixel) {
1099             case 4 :
1100             for (vertical = 0; vertical < h; vertical++) {
1101             unsigned int vbl = vertical * bline;
1102             unsigned short yv = fb_y + vertical;
1103             unsigned int yvbl = yv * bytes_per_line;
1104             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1105             for (horizontal = 0; horizontal < w; horizontal++) {
1106             unsigned short xh = fb_x + horizontal;
1107             unsigned int xhbp = xh * bytes_per_pixel;
1108             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1109             unsigned int hzpixel = horizontal * bytes_per_pixel;
1110             unsigned int vhz = vbl + hzpixel;
1111             unsigned int yvhz = yvbl + hzpixel;
1112             unsigned int xhbp_yvbl = xhbp + yvbl;
1113             *((unsigned int*)(framebuffer + xhbp_yvbl)) ^= *((unsigned int*)(blit_data + vhz));
1114             }
1115             }
1116             }
1117             }
1118             break;
1119             case 3 :
1120             for (vertical = 0; vertical < h; vertical++) {
1121             unsigned int vbl = vertical * bline;
1122             unsigned short yv = fb_y + vertical;
1123             unsigned int yvbl = yv * bytes_per_line;
1124             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1125             for (horizontal = 0; horizontal < w; horizontal++) {
1126             unsigned short xh = fb_x + horizontal;
1127             unsigned int xhbp = xh * bytes_per_pixel;
1128             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1129             unsigned int hzpixel = horizontal * bytes_per_pixel;
1130             unsigned int vhz = vbl + hzpixel;
1131             unsigned int yvhz = yvbl + hzpixel;
1132             unsigned int xhbp_yvbl = xhbp + yvbl;
1133             *(framebuffer + xhbp_yvbl ) ^= *(blit_data + vhz );
1134             *(framebuffer + xhbp_yvbl + 1) ^= *(blit_data + vhz + 1);
1135             *(framebuffer + xhbp_yvbl + 2) ^= *(blit_data + vhz + 2);
1136             }
1137             }
1138             }
1139             }
1140             break;
1141             case 2 :
1142             for (vertical = 0; vertical < h; vertical++) {
1143             unsigned int vbl = vertical * bline;
1144             unsigned short yv = fb_y + vertical;
1145             unsigned int yvbl = yv * bytes_per_line;
1146             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1147             for (horizontal = 0; horizontal < w; horizontal++) {
1148             unsigned short xh = fb_x + horizontal;
1149             unsigned int xhbp = xh * bytes_per_pixel;
1150             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1151             unsigned int hzpixel = horizontal * bytes_per_pixel;
1152             unsigned int vhz = vbl + hzpixel;
1153             unsigned int yvhz = yvbl + hzpixel;
1154             unsigned int xhbp_yvbl = xhbp + yvbl;
1155             *((unsigned short*)(framebuffer + xhbp_yvbl )) ^= *((unsigned short*)(blit_data + vhz ));
1156             }
1157             }
1158             }
1159             }
1160             break;
1161             }
1162             break;
1163             case OR_MODE :
1164             switch(bytes_per_pixel) {
1165             case 4 :
1166             for (vertical = 0; vertical < h; vertical++) {
1167             unsigned int vbl = vertical * bline;
1168             unsigned short yv = fb_y + vertical;
1169             unsigned int yvbl = yv * bytes_per_line;
1170             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1171             for (horizontal = 0; horizontal < w; horizontal++) {
1172             unsigned short xh = fb_x + horizontal;
1173             unsigned int xhbp = xh * bytes_per_pixel;
1174             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1175             unsigned int hzpixel = horizontal * bytes_per_pixel;
1176             unsigned int vhz = vbl + hzpixel;
1177             unsigned int yvhz = yvbl + hzpixel;
1178             unsigned int xhbp_yvbl = xhbp + yvbl;
1179             *((unsigned int*)(framebuffer + xhbp_yvbl)) |= *((unsigned int*)(blit_data + vhz));
1180             }
1181             }
1182             }
1183             }
1184             break;
1185             case 3 :
1186             for (vertical = 0; vertical < h; vertical++) {
1187             unsigned int vbl = vertical * bline;
1188             unsigned short yv = fb_y + vertical;
1189             unsigned int yvbl = yv * bytes_per_line;
1190             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1191             for (horizontal = 0; horizontal < w; horizontal++) {
1192             unsigned short xh = fb_x + horizontal;
1193             unsigned int xhbp = xh * bytes_per_pixel;
1194             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1195             unsigned int hzpixel = horizontal * bytes_per_pixel;
1196             unsigned int vhz = vbl + hzpixel;
1197             unsigned int yvhz = yvbl + hzpixel;
1198             unsigned int xhbp_yvbl = xhbp + yvbl;
1199             *(framebuffer + xhbp_yvbl ) |= *(blit_data + vhz );
1200             *(framebuffer + xhbp_yvbl + 1) |= *(blit_data + vhz + 1);
1201             *(framebuffer + xhbp_yvbl + 2) |= *(blit_data + vhz + 2);
1202             }
1203             }
1204             }
1205             }
1206             break;
1207             case 2 :
1208             for (vertical = 0; vertical < h; vertical++) {
1209             unsigned int vbl = vertical * bline;
1210             unsigned short yv = fb_y + vertical;
1211             unsigned int yvbl = yv * bytes_per_line;
1212             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1213             for (horizontal = 0; horizontal < w; horizontal++) {
1214             unsigned short xh = fb_x + horizontal;
1215             unsigned int xhbp = xh * bytes_per_pixel;
1216             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1217             unsigned int hzpixel = horizontal * bytes_per_pixel;
1218             unsigned int vhz = vbl + hzpixel;
1219             unsigned int yvhz = yvbl + hzpixel;
1220             unsigned int xhbp_yvbl = xhbp + yvbl;
1221             *((unsigned short*)(framebuffer + xhbp_yvbl )) |= *((unsigned short*)(blit_data + vhz ));
1222             }
1223             }
1224             }
1225             }
1226             break;
1227             }
1228             break;
1229             case AND_MODE :
1230             switch(bytes_per_pixel) {
1231             case 4 :
1232             for (vertical = 0; vertical < h; vertical++) {
1233             unsigned int vbl = vertical * bline;
1234             unsigned short yv = fb_y + vertical;
1235             unsigned int yvbl = yv * bytes_per_line;
1236             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1237             for (horizontal = 0; horizontal < w; horizontal++) {
1238             unsigned short xh = fb_x + horizontal;
1239             unsigned int xhbp = xh * bytes_per_pixel;
1240             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1241             unsigned int hzpixel = horizontal * bytes_per_pixel;
1242             unsigned int vhz = vbl + hzpixel;
1243             unsigned int yvhz = yvbl + hzpixel;
1244             unsigned int xhbp_yvbl = xhbp + yvbl;
1245             *((unsigned int*)(framebuffer + xhbp_yvbl)) &= *((unsigned int*)(blit_data + vhz));
1246             }
1247             }
1248             }
1249             }
1250             break;
1251             case 3 :
1252             for (vertical = 0; vertical < h; vertical++) {
1253             unsigned int vbl = vertical * bline;
1254             unsigned short yv = fb_y + vertical;
1255             unsigned int yvbl = yv * bytes_per_line;
1256             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1257             for (horizontal = 0; horizontal < w; horizontal++) {
1258             unsigned short xh = fb_x + horizontal;
1259             unsigned int xhbp = xh * bytes_per_pixel;
1260             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1261             unsigned int hzpixel = horizontal * bytes_per_pixel;
1262             unsigned int vhz = vbl + hzpixel;
1263             unsigned int yvhz = yvbl + hzpixel;
1264             unsigned int xhbp_yvbl = xhbp + yvbl;
1265             *(framebuffer + xhbp_yvbl ) &= *(blit_data + vhz );
1266             *(framebuffer + xhbp_yvbl + 1) &= *(blit_data + vhz + 1);
1267             *(framebuffer + xhbp_yvbl + 2) &= *(blit_data + vhz + 2);
1268             }
1269             }
1270             }
1271             }
1272             break;
1273             case 2 :
1274             for (vertical = 0; vertical < h; vertical++) {
1275             unsigned int vbl = vertical * bline;
1276             unsigned short yv = fb_y + vertical;
1277             unsigned int yvbl = yv * bytes_per_line;
1278             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1279             for (horizontal = 0; horizontal < w; horizontal++) {
1280             unsigned short xh = fb_x + horizontal;
1281             unsigned int xhbp = xh * bytes_per_pixel;
1282             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1283             unsigned int hzpixel = horizontal * bytes_per_pixel;
1284             unsigned int vhz = vbl + hzpixel;
1285             unsigned int yvhz = yvbl + hzpixel;
1286             unsigned int xhbp_yvbl = xhbp + yvbl;
1287             *((unsigned short*)(framebuffer + xhbp_yvbl )) &= *((unsigned short*)(blit_data + vhz ));
1288             }
1289             }
1290             }
1291             }
1292             break;
1293             }
1294             break;
1295             case MASK_MODE :
1296             switch(bytes_per_pixel) {
1297             case 4 :
1298             for (vertical = 0; vertical < h; vertical++) {
1299             unsigned int vbl = vertical * bline;
1300             unsigned short yv = fb_y + vertical;
1301             unsigned int yvbl = yv * bytes_per_line;
1302             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1303             for (horizontal = 0; horizontal < w; horizontal++) {
1304             unsigned short xh = fb_x + horizontal;
1305             unsigned int xhbp = xh * bytes_per_pixel;
1306             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1307             unsigned int hzpixel = horizontal * bytes_per_pixel;
1308             unsigned int vhz = vbl + hzpixel;
1309             unsigned int yvhz = yvbl + hzpixel;
1310             unsigned int xhbp_yvbl = xhbp + yvbl;
1311             unsigned int rgb = *((unsigned int*)(blit_data + vhz ));
1312             if (( rgb & 0xFFFFFF00) != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
1313             *((unsigned int*)(framebuffer + xhbp_yvbl )) = rgb;
1314             }
1315             }
1316             }
1317             }
1318             }
1319             break;
1320             case 3 :
1321             for (vertical = 0; vertical < h; vertical++) {
1322             unsigned int vbl = vertical * bline;
1323             unsigned short yv = fb_y + vertical;
1324             unsigned int yvbl = yv * bytes_per_line;
1325             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1326             for (horizontal = 0; horizontal < w; horizontal++) {
1327             unsigned short xh = fb_x + horizontal;
1328             unsigned int xhbp = xh * bytes_per_pixel;
1329             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1330             unsigned int hzpixel = horizontal * bytes_per_pixel;
1331             unsigned int vhz = vbl + hzpixel;
1332             unsigned int yvhz = yvbl + hzpixel;
1333             unsigned int xhbp_yvbl = xhbp + yvbl;
1334             if ((*((unsigned int*)(blit_data + vhz )) & 0xFFFFFF00) != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
1335             *(framebuffer + xhbp_yvbl ) = *(blit_data + vhz );
1336             *(framebuffer + xhbp_yvbl + 1) = *(blit_data + vhz + 1);
1337             *(framebuffer + xhbp_yvbl + 2) = *(blit_data + vhz + 2);
1338             }
1339             }
1340             }
1341             }
1342             }
1343             break;
1344             case 2 :
1345             for (vertical = 0; vertical < h; vertical++) {
1346             unsigned int vbl = vertical * bline;
1347             unsigned short yv = fb_y + vertical;
1348             unsigned int yvbl = yv * bytes_per_line;
1349             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1350             for (horizontal = 0; horizontal < w; horizontal++) {
1351             unsigned short xh = fb_x + horizontal;
1352             unsigned int xhbp = xh * bytes_per_pixel;
1353             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1354             unsigned int hzpixel = horizontal * bytes_per_pixel;
1355             unsigned int vhz = vbl + hzpixel;
1356             unsigned int yvhz = yvbl + hzpixel;
1357             unsigned int xhbp_yvbl = xhbp + yvbl;
1358             unsigned int rgb = *((unsigned short*)(blit_data + vhz ));
1359             if (rgb != (bcolor & 0xFFFF)) {
1360             *((unsigned short*)(framebuffer + xhbp_yvbl )) = rgb;
1361             }
1362             }
1363             }
1364             }
1365             }
1366             break;
1367             }
1368             break;
1369             case UNMASK_MODE :
1370             switch(bytes_per_pixel) {
1371             case 4 :
1372             for (vertical = 0; vertical < h; vertical++) {
1373             unsigned int vbl = vertical * bline;
1374             unsigned short yv = fb_y + vertical;
1375             unsigned int yvbl = yv * bytes_per_line;
1376             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1377             for (horizontal = 0; horizontal < w; horizontal++) {
1378             unsigned short xh = fb_x + horizontal;
1379             unsigned int xhbp = xh * bytes_per_pixel;
1380             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1381             unsigned int hzpixel = horizontal * bytes_per_pixel;
1382             unsigned int vhz = vbl + hzpixel;
1383             unsigned int yvhz = yvbl + hzpixel;
1384             unsigned int xhbp_yvbl = xhbp + yvbl;
1385             if ((*((unsigned int*)(framebuffer + xhbp_yvbl )) & 0xFFFFFF00) == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel for color testing
1386             *((unsigned int*)(framebuffer + xhbp_yvbl )) = *((unsigned int*)(blit_data + vhz ));
1387             }
1388             }
1389             }
1390             }
1391             }
1392             break;
1393             case 3 :
1394             for (vertical = 0; vertical < h; vertical++) {
1395             unsigned int vbl = vertical * bline;
1396             unsigned short yv = fb_y + vertical;
1397             unsigned int yvbl = yv * bytes_per_line;
1398             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1399             for (horizontal = 0; horizontal < w; horizontal++) {
1400             unsigned short xh = fb_x + horizontal;
1401             unsigned int xhbp = xh * bytes_per_pixel;
1402             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1403             unsigned int hzpixel = horizontal * bytes_per_pixel;
1404             unsigned int vhz = vbl + hzpixel;
1405             unsigned int yvhz = yvbl + hzpixel;
1406             unsigned int xhbp_yvbl = xhbp + yvbl;
1407             if (*((unsigned int*)(framebuffer + xhbp + yvhz )) == (bcolor & 0xFFFFFF00)) {
1408             *(framebuffer + xhbp_yvbl ) = *(blit_data + vhz );
1409             *(framebuffer + xhbp_yvbl + 1) = *(blit_data + vhz + 1);
1410             *(framebuffer + xhbp_yvbl + 2) = *(blit_data + vhz + 2);
1411             }
1412             }
1413             }
1414             }
1415             }
1416             break;
1417             case 2 :
1418             for (vertical = 0; vertical < h; vertical++) {
1419             unsigned int vbl = vertical * bline;
1420             unsigned short yv = fb_y + vertical;
1421             unsigned int yvbl = yv * bytes_per_line;
1422             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1423             for (horizontal = 0; horizontal < w; horizontal++) {
1424             unsigned short xh = fb_x + horizontal;
1425             unsigned int xhbp = xh * bytes_per_pixel;
1426             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1427             unsigned int hzpixel = horizontal * bytes_per_pixel;
1428             unsigned int vhz = vbl + hzpixel;
1429             unsigned int yvhz = yvbl + hzpixel;
1430             unsigned int xhbp_yvbl = xhbp + yvbl;
1431             if (*((unsigned short*)(framebuffer + xhbp + yvhz )) == (bcolor & 0xFFFF)) {
1432             *((unsigned short*)(framebuffer + xhbp_yvbl )) = *((unsigned short*)(blit_data + vhz ));
1433             }
1434             }
1435             }
1436             }
1437             }
1438             break;
1439             }
1440             break;
1441             case ALPHA_MODE :
1442             switch(bytes_per_pixel) {
1443             case 4 :
1444             for (vertical = 0; vertical < h; vertical++) {
1445             unsigned int vbl = vertical * bline;
1446             unsigned short yv = fb_y + vertical;
1447             unsigned int yvbl = yv * bytes_per_line;
1448             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1449             for (horizontal = 0; horizontal < w; horizontal++) {
1450             unsigned short xh = fb_x + horizontal;
1451             unsigned int xhbp = xh * bytes_per_pixel;
1452             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1453             unsigned int hzpixel = horizontal * bytes_per_pixel;
1454             unsigned int vhz = vbl + hzpixel;
1455             unsigned int yvhz = yvbl + hzpixel;
1456             unsigned int xhbp_yvbl = xhbp + yvbl;
1457              
1458             unsigned int fb_rgb = *((unsigned int*)(framebuffer + xhbp_yvbl));
1459             unsigned char fb_r = fb_rgb & 255;
1460             unsigned char fb_g = (fb_rgb >> 8) & 255;
1461             unsigned char fb_b = (fb_rgb >> 16) & 255;
1462              
1463             unsigned int blit_rgb = *((unsigned int*)(blit_data + vhz));
1464             unsigned char R = blit_rgb & 255;
1465             unsigned char G = (blit_rgb >> 8) & 255;
1466             unsigned char B = (blit_rgb >> 16) & 255;
1467             unsigned char A = (blit_rgb >> 24) & 255;
1468             unsigned char invA = (255 - A);
1469              
1470             fb_r = ((R * A) + (fb_r * invA)) >> 8;
1471             fb_g = ((G * A) + (fb_g * invA)) >> 8;
1472             fb_b = ((B * A) + (fb_b * invA)) >> 8;
1473              
1474             *((unsigned int*)(framebuffer + xhbp_yvbl)) = fb_r | (fb_g << 8) | (fb_b << 16) | (A << 24);
1475             }
1476             }
1477             }
1478             }
1479             break;
1480             case 3 :
1481             for (vertical = 0; vertical < h; vertical++) {
1482             unsigned int vbl = vertical * bline;
1483             unsigned short yv = fb_y + vertical;
1484             unsigned int yvbl = yv * bytes_per_line;
1485             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1486             for (horizontal = 0; horizontal < w; horizontal++) {
1487             unsigned short xh = fb_x + horizontal;
1488             unsigned int xhbp = xh * bytes_per_pixel;
1489             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1490             unsigned int hzpixel = horizontal * bytes_per_pixel;
1491             unsigned int vhz = vbl + hzpixel;
1492             unsigned int yvhz = yvbl + hzpixel;
1493             unsigned int xhbp_yvbl = xhbp + yvbl;
1494              
1495             unsigned char fb_r = *(framebuffer + xhbp_yvbl );
1496             unsigned char fb_g = *(framebuffer + xhbp_yvbl + 1);
1497             unsigned char fb_b = *(framebuffer + xhbp_yvbl + 2);
1498             unsigned char R = *(blit_data + vhz );
1499             unsigned char G = *(blit_data + vhz + 1);
1500             unsigned char B = *(blit_data + vhz + 2);
1501             unsigned char invA = (255 - alpha);
1502              
1503             fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
1504             fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
1505             fb_b = ((B * alpha) + (fb_b * invA)) >> 8;
1506              
1507             *(framebuffer + xhbp_yvbl ) = fb_r;
1508             *(framebuffer + xhbp_yvbl + 1) = fb_g;
1509             *(framebuffer + xhbp_yvbl + 2) = fb_b;
1510             }
1511             }
1512             }
1513             }
1514             break;
1515             case 2 :
1516             for (vertical = 0; vertical < h; vertical++) {
1517             unsigned int vbl = vertical * bline;
1518             unsigned short yv = fb_y + vertical;
1519             unsigned int yvbl = yv * bytes_per_line;
1520             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1521             for (horizontal = 0; horizontal < w; horizontal++) {
1522             unsigned short xh = fb_x + horizontal;
1523             unsigned int xhbp = xh * bytes_per_pixel;
1524             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1525             unsigned int hzpixel = horizontal * bytes_per_pixel;
1526             unsigned int vhz = vbl + hzpixel;
1527             unsigned int yvhz = yvbl + hzpixel;
1528             unsigned int xhbp_yvbl = xhbp + yvbl;
1529             unsigned short rgb565 = *((unsigned short*)(framebuffer + xhbp_yvbl ));
1530              
1531             unsigned short fb_r = rgb565 & 31;
1532             unsigned short fb_g = (rgb565 >> 5) & 63;
1533             unsigned short fb_b = (rgb565 >> 11) & 31;
1534             rgb565 = *((unsigned short*)(blit_data + vhz ));
1535             unsigned short R = rgb565 & 31;
1536             unsigned short G = (rgb565 >> 5) & 63;
1537             unsigned short B = (rgb565 >> 11) & 31;
1538             unsigned char invA = (255 - alpha);
1539             fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
1540             fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
1541             fb_b = ((B * alpha) + (fb_b * invA)) >> 8;
1542              
1543             *((unsigned short*)(framebuffer + xhbp_yvbl )) = (fb_b << 11) | (fb_g << 5) | fb_r;
1544              
1545             }
1546             }
1547             }
1548             }
1549             break;
1550             }
1551             break;
1552             case ADD_MODE :
1553             switch(bytes_per_pixel) {
1554             case 4 :
1555             for (vertical = 0; vertical < h; vertical++) {
1556             unsigned int vbl = vertical * bline;
1557             unsigned short yv = fb_y + vertical;
1558             unsigned int yvbl = yv * bytes_per_line;
1559             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1560             for (horizontal = 0; horizontal < w; horizontal++) {
1561             unsigned short xh = fb_x + horizontal;
1562             unsigned int xhbp = xh * bytes_per_pixel;
1563             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1564             unsigned int hzpixel = horizontal * bytes_per_pixel;
1565             unsigned int vhz = vbl + hzpixel;
1566             unsigned int yvhz = yvbl + hzpixel;
1567             unsigned int xhbp_yvbl = xhbp + yvbl;
1568             *((unsigned int*)(framebuffer + xhbp_yvbl)) += *((unsigned int*)(blit_data + vhz));
1569             }
1570             }
1571             }
1572             }
1573             break;
1574             case 3 :
1575             for (vertical = 0; vertical < h; vertical++) {
1576             unsigned int vbl = vertical * bline;
1577             unsigned short yv = fb_y + vertical;
1578             unsigned int yvbl = yv * bytes_per_line;
1579             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1580             for (horizontal = 0; horizontal < w; horizontal++) {
1581             unsigned short xh = fb_x + horizontal;
1582             unsigned int xhbp = xh * bytes_per_pixel;
1583             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1584             unsigned int hzpixel = horizontal * bytes_per_pixel;
1585             unsigned int vhz = vbl + hzpixel;
1586             unsigned int yvhz = yvbl + hzpixel;
1587             unsigned int xhbp_yvbl = xhbp + yvbl;
1588             *(framebuffer + xhbp_yvbl ) += *(blit_data + vhz );
1589             *(framebuffer + xhbp_yvbl + 1) += *(blit_data + vhz + 1);
1590             *(framebuffer + xhbp_yvbl + 2) += *(blit_data + vhz + 2);
1591             }
1592             }
1593             }
1594             }
1595             break;
1596             case 2 :
1597             for (vertical = 0; vertical < h; vertical++) {
1598             unsigned int vbl = vertical * bline;
1599             unsigned short yv = fb_y + vertical;
1600             unsigned int yvbl = yv * bytes_per_line;
1601             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1602             for (horizontal = 0; horizontal < w; horizontal++) {
1603             unsigned short xh = fb_x + horizontal;
1604             unsigned int xhbp = xh * bytes_per_pixel;
1605             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1606             unsigned int hzpixel = horizontal * bytes_per_pixel;
1607             unsigned int vhz = vbl + hzpixel;
1608             unsigned int yvhz = yvbl + hzpixel;
1609             unsigned int xhbp_yvbl = xhbp + yvbl;
1610             *((unsigned short*)(framebuffer + xhbp_yvbl )) += *((unsigned short*)(blit_data + vhz ));
1611             }
1612             }
1613             }
1614             }
1615             break;
1616             }
1617             break;
1618             case SUBTRACT_MODE :
1619             switch(bytes_per_pixel) {
1620             case 4 :
1621             for (vertical = 0; vertical < h; vertical++) {
1622             unsigned int vbl = vertical * bline;
1623             unsigned short yv = fb_y + vertical;
1624             unsigned int yvbl = yv * bytes_per_line;
1625             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1626             for (horizontal = 0; horizontal < w; horizontal++) {
1627             unsigned short xh = fb_x + horizontal;
1628             unsigned int xhbp = xh * bytes_per_pixel;
1629             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1630             unsigned int hzpixel = horizontal * bytes_per_pixel;
1631             unsigned int vhz = vbl + hzpixel;
1632             unsigned int yvhz = yvbl + hzpixel;
1633             unsigned int xhbp_yvbl = xhbp + yvbl;
1634             *((unsigned int*)(framebuffer + xhbp_yvbl)) -= *((unsigned int*)(blit_data + vhz));
1635             }
1636             }
1637             }
1638             }
1639             break;
1640             case 3 :
1641             for (vertical = 0; vertical < h; vertical++) {
1642             unsigned int vbl = vertical * bline;
1643             unsigned short yv = fb_y + vertical;
1644             unsigned int yvbl = yv * bytes_per_line;
1645             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1646             for (horizontal = 0; horizontal < w; horizontal++) {
1647             unsigned short xh = fb_x + horizontal;
1648             unsigned int xhbp = xh * bytes_per_pixel;
1649             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1650             unsigned int hzpixel = horizontal * bytes_per_pixel;
1651             unsigned int vhz = vbl + hzpixel;
1652             unsigned int yvhz = yvbl + hzpixel;
1653             unsigned int xhbp_yvbl = xhbp + yvbl;
1654             *(framebuffer + xhbp_yvbl ) -= *(blit_data + vhz );
1655             *(framebuffer + xhbp_yvbl + 1) -= *(blit_data + vhz + 1);
1656             *(framebuffer + xhbp_yvbl + 2) -= *(blit_data + vhz + 2);
1657             }
1658             }
1659             }
1660             }
1661             break;
1662             case 2 :
1663             for (vertical = 0; vertical < h; vertical++) {
1664             unsigned int vbl = vertical * bline;
1665             unsigned short yv = fb_y + vertical;
1666             unsigned int yvbl = yv * bytes_per_line;
1667             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1668             for (horizontal = 0; horizontal < w; horizontal++) {
1669             unsigned short xh = fb_x + horizontal;
1670             unsigned int xhbp = xh * bytes_per_pixel;
1671             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1672             unsigned int hzpixel = horizontal * bytes_per_pixel;
1673             unsigned int vhz = vbl + hzpixel;
1674             unsigned int yvhz = yvbl + hzpixel;
1675             unsigned int xhbp_yvbl = xhbp + yvbl;
1676             *((unsigned short*)(framebuffer + xhbp_yvbl )) -= *((unsigned short*)(blit_data + vhz ));
1677             }
1678             }
1679             }
1680             }
1681             break;
1682             }
1683             break;
1684             case MULTIPLY_MODE :
1685             switch(bytes_per_pixel) {
1686             case 4 :
1687             for (vertical = 0; vertical < h; vertical++) {
1688             unsigned int vbl = vertical * bline;
1689             unsigned short yv = fb_y + vertical;
1690             unsigned int yvbl = yv * bytes_per_line;
1691             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1692             for (horizontal = 0; horizontal < w; horizontal++) {
1693             unsigned short xh = fb_x + horizontal;
1694             unsigned int xhbp = xh * bytes_per_pixel;
1695             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1696             unsigned int hzpixel = horizontal * bytes_per_pixel;
1697             unsigned int vhz = vbl + hzpixel;
1698             unsigned int yvhz = yvbl + hzpixel;
1699             unsigned int xhbp_yvbl = xhbp + yvbl;
1700             *((unsigned int*)(framebuffer + xhbp_yvbl)) *= *((unsigned int*)(blit_data + vhz));
1701             }
1702             }
1703             }
1704             }
1705             break;
1706             case 3 :
1707             for (vertical = 0; vertical < h; vertical++) {
1708             unsigned int vbl = vertical * bline;
1709             unsigned short yv = fb_y + vertical;
1710             unsigned int yvbl = yv * bytes_per_line;
1711             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1712             for (horizontal = 0; horizontal < w; horizontal++) {
1713             unsigned short xh = fb_x + horizontal;
1714             unsigned int xhbp = xh * bytes_per_pixel;
1715             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1716             unsigned int hzpixel = horizontal * bytes_per_pixel;
1717             unsigned int vhz = vbl + hzpixel;
1718             unsigned int yvhz = yvbl + hzpixel;
1719             unsigned int xhbp_yvbl = xhbp + yvbl;
1720             *(framebuffer + xhbp_yvbl ) *= *(blit_data + vhz );
1721             *(framebuffer + xhbp_yvbl + 1) *= *(blit_data + vhz + 1);
1722             *(framebuffer + xhbp_yvbl + 2) *= *(blit_data + vhz + 2);
1723             }
1724             }
1725             }
1726             }
1727             break;
1728             case 2 :
1729             for (vertical = 0; vertical < h; vertical++) {
1730             unsigned int vbl = vertical * bline;
1731             unsigned short yv = fb_y + vertical;
1732             unsigned int yvbl = yv * bytes_per_line;
1733             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1734             for (horizontal = 0; horizontal < w; horizontal++) {
1735             unsigned short xh = fb_x + horizontal;
1736             unsigned int xhbp = xh * bytes_per_pixel;
1737             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1738             unsigned int hzpixel = horizontal * bytes_per_pixel;
1739             unsigned int vhz = vbl + hzpixel;
1740             unsigned int yvhz = yvbl + hzpixel;
1741             unsigned int xhbp_yvbl = xhbp + yvbl;
1742             *((unsigned short*)(framebuffer + xhbp_yvbl )) *= *((unsigned short*)(blit_data + vhz ));
1743             }
1744             }
1745             }
1746             }
1747             break;
1748             }
1749             break;
1750             case DIVIDE_MODE :
1751             switch(bytes_per_pixel) {
1752             case 4 :
1753             for (vertical = 0; vertical < h; vertical++) {
1754             unsigned int vbl = vertical * bline;
1755             unsigned short yv = fb_y + vertical;
1756             unsigned int yvbl = yv * bytes_per_line;
1757             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1758             for (horizontal = 0; horizontal < w; horizontal++) {
1759             unsigned short xh = fb_x + horizontal;
1760             unsigned int xhbp = xh * bytes_per_pixel;
1761             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1762             unsigned int hzpixel = horizontal * bytes_per_pixel;
1763             unsigned int vhz = vbl + hzpixel;
1764             unsigned int yvhz = yvbl + hzpixel;
1765             unsigned int xhbp_yvbl = xhbp + yvbl;
1766             *((unsigned int*)(framebuffer + xhbp_yvbl)) /= *((unsigned int*)(blit_data + vhz));
1767             }
1768             }
1769             }
1770             }
1771             break;
1772             case 3 :
1773             for (vertical = 0; vertical < h; vertical++) {
1774             unsigned int vbl = vertical * bline;
1775             unsigned short yv = fb_y + vertical;
1776             unsigned int yvbl = yv * bytes_per_line;
1777             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1778             for (horizontal = 0; horizontal < w; horizontal++) {
1779             unsigned short xh = fb_x + horizontal;
1780             unsigned int xhbp = xh * bytes_per_pixel;
1781             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1782             unsigned int hzpixel = horizontal * bytes_per_pixel;
1783             unsigned int vhz = vbl + hzpixel;
1784             unsigned int yvhz = yvbl + hzpixel;
1785             unsigned int xhbp_yvbl = xhbp + yvbl;
1786             *(framebuffer + xhbp_yvbl ) /= *(blit_data + vhz );
1787             *(framebuffer + xhbp_yvbl + 1) /= *(blit_data + vhz + 1);
1788             *(framebuffer + xhbp_yvbl + 2) /= *(blit_data + vhz + 2);
1789             }
1790             }
1791             }
1792             }
1793             break;
1794             case 2 :
1795             for (vertical = 0; vertical < h; vertical++) {
1796             unsigned int vbl = vertical * bline;
1797             unsigned short yv = fb_y + vertical;
1798             unsigned int yvbl = yv * bytes_per_line;
1799             if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
1800             for (horizontal = 0; horizontal < w; horizontal++) {
1801             unsigned short xh = fb_x + horizontal;
1802             unsigned int xhbp = xh * bytes_per_pixel;
1803             if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
1804             unsigned int hzpixel = horizontal * bytes_per_pixel;
1805             unsigned int vhz = vbl + hzpixel;
1806             unsigned int yvhz = yvbl + hzpixel;
1807             unsigned int xhbp_yvbl = xhbp + yvbl;
1808             *((unsigned short*)(framebuffer + xhbp_yvbl )) /= *((unsigned short*)(blit_data + vhz ));
1809             }
1810             }
1811             }
1812             }
1813             break;
1814             }
1815             break;
1816             }
1817             }
1818             }
1819              
1820             // Fast rotate blit graphics data
1821             void c_rotate(
1822             char *image,
1823             char *new_img,
1824             short width, short height,
1825             unsigned short wh,
1826             double degrees,
1827             unsigned char bytes_per_pixel)
1828             {
1829             unsigned int hwh = floor(wh / 2 + 0.5);
1830             unsigned int bbline = wh * bytes_per_pixel;
1831             unsigned int bline = width * bytes_per_pixel;
1832             unsigned short hwidth = floor(width / 2 + 0.5);
1833             unsigned short hheight = floor(height / 2 + 0.5);
1834             double sinma = sin((degrees * M_PI) / 180);
1835             double cosma = cos((degrees * M_PI) / 180);
1836             short x;
1837             short y;
1838              
1839             for (x = 0; x < wh; x++) {
1840             short xt = x - hwh;
1841             for (y = 0; y < wh; y++) {
1842             short yt = y - hwh;
1843             short xs = ((cosma * xt - sinma * yt) + hwidth);
1844             short ys = ((sinma * xt + cosma * yt) + hheight);
1845             if (xs >= 0 && xs < width && ys >= 0 && ys < height) {
1846             switch(bytes_per_pixel) {
1847             case 4 :
1848             {
1849             *((unsigned int*)(new_img + (x * bytes_per_pixel) + (y * bbline))) = *((unsigned int*)(image + (xs * bytes_per_pixel) + (ys * bline)));
1850             }
1851             break;
1852             case 3 :
1853             {
1854             *(new_img + (x * bytes_per_pixel) + (y * bbline)) = *(image + (xs * bytes_per_pixel) + (ys * bline));
1855             *(new_img + (x * bytes_per_pixel) + (y * bbline) + 1) = *(image + (xs * bytes_per_pixel) + (ys * bline) + 1);
1856             *(new_img + (x * bytes_per_pixel) + (y * bbline) + 2) = *(image + (xs * bytes_per_pixel) + (ys * bline) + 2);
1857             }
1858             break;
1859             case 2 :
1860             {
1861             *((unsigned short*)(new_img + (x * bytes_per_pixel) + (y * bbline))) = *((unsigned short*)(image + (xs * bytes_per_pixel) + (ys * bline)));
1862             }
1863             break;
1864             }
1865             }
1866             }
1867             }
1868             }
1869              
1870             // Horizontally mirror blit graphics data
1871             void c_flip_horizontal(char* pixels, short width, short height, unsigned char bytes_per_pixel) {
1872             short y;
1873             short x;
1874             unsigned short offset;
1875             unsigned char left;
1876             unsigned int bpl = width * bytes_per_pixel;
1877             unsigned short hwidth = width / 2;
1878             for ( y = 0; y < height; y++ ) {
1879             unsigned int ydx = y * bpl;
1880             for (x = 0; x < hwidth ; x++) { // Stop when you reach the middle
1881             for (offset = 0; offset < bytes_per_pixel; offset++) {
1882             left = *(pixels + (x * bytes_per_pixel) + ydx + offset);
1883             *(pixels + (x * bytes_per_pixel) + ydx + offset) = *(pixels + ((width - x) * bytes_per_pixel) + ydx + offset);
1884             *(pixels + ((width - x) * bytes_per_pixel) + ydx + offset) = left;
1885             }
1886             }
1887             }
1888             }
1889              
1890             // Vertically flip blit graphics data
1891             void c_flip_vertical(char *pixels, short width, short height, unsigned char bytes_per_pixel) {
1892             unsigned int bufsize = width * bytes_per_pixel; // Bytes per line
1893             unsigned char *row = malloc(bufsize); // Allocate a temporary buffer
1894             unsigned char *low = pixels; // Pointer to the beginning of the image
1895             unsigned char *high = &pixels[(height - 1) * bufsize]; // Pointer to the last line in the image
1896              
1897             for (; low < high; low += bufsize, high -= bufsize) { // Stop when you reach the middle
1898             memcpy(row,low,bufsize); // Make a copy of the lower line
1899             memcpy(low,high,bufsize); // Copy the upper line to the lower
1900             memcpy(high, row, bufsize); // Copy the saved copy to the upper line
1901             }
1902             free(row); // Release the temporary buffer
1903             }
1904              
1905             // Horizontally and vertically flip blit graphics data
1906             void c_flip_both(char* pixels, short width, short height, unsigned char bytes_per_pixel) {
1907             c_flip_vertical(
1908             pixels,
1909             width,height,
1910             bytes_per_pixel
1911             );
1912             c_flip_horizontal(
1913             pixels,
1914             width,height,
1915             bytes_per_pixel
1916             );
1917             }
1918              
1919             /* bitmap conversions */
1920              
1921             // Convert an RGB565 bitmap to an RGB888 bitmap
1922             void c_convert_16_24( char* buf16, unsigned int size16, char* buf24, unsigned char color_order ) {
1923             unsigned int loc16 = 0;
1924             unsigned int loc24 = 0;
1925             unsigned char r5;
1926             unsigned char g6;
1927             unsigned char b5;
1928              
1929             while(loc16 < size16) {
1930             unsigned short rgb565 = *((unsigned short*)(buf16 + loc16));
1931             loc16 += 2;
1932             if (color_order == RGB) {
1933             b5 = (rgb565 & 0xf800) >> 11;
1934             r5 = (rgb565 & 0x001f);
1935             } else {
1936             r5 = (rgb565 & 0xf800) >> 11;
1937             b5 = (rgb565 & 0x001f);
1938             }
1939             g6 = (rgb565 & 0x07e0) >> 5;
1940             unsigned char r8 = (r5 * 527 + 23) >> 6;
1941             unsigned char g8 = (g6 * 259 + 33) >> 6;
1942             unsigned char b8 = (b5 * 527 + 23) >> 6;
1943             *((unsigned char*)(buf24 + loc24++)) = r8;
1944             *((unsigned char*)(buf24 + loc24++)) = g8;
1945             *((unsigned char*)(buf24 + loc24++)) = b8;
1946             }
1947             }
1948              
1949             // Convert an RGB565 bitmap to a RGB8888 bitmap
1950             void c_convert_16_32( char* buf16, unsigned int size16, char* buf32, unsigned char color_order ) {
1951             unsigned int loc16 = 0;
1952             unsigned int loc32 = 0;
1953             unsigned char r5;
1954             unsigned char g6;
1955             unsigned char b5;
1956              
1957             while(loc16 < size16) {
1958             unsigned short rgb565 = *((unsigned short*)(buf16 + loc16));
1959             loc16 += 2;
1960             if (color_order == 0) {
1961             b5 = (rgb565 & 0xf800) >> 11;
1962             r5 = (rgb565 & 0x001f);
1963             } else {
1964             r5 = (rgb565 & 0xf800) >> 11;
1965             b5 = (rgb565 & 0x001f);
1966             }
1967             g6 = (rgb565 & 0x07e0) >> 5;
1968             unsigned char r8 = (r5 * 527 + 23) >> 6;
1969             unsigned char g8 = (g6 * 259 + 33) >> 6;
1970             unsigned char b8 = (b5 * 527 + 23) >> 6;
1971             *((unsigned int*)(buf32 + loc32)) = r8 | (g8 << 8) | (b8 << 16);
1972             loc32 += 3;
1973             if (r8 == 0 && g8 == 0 && b8 ==0) { // Black is always treated as a clear mask
1974             *((unsigned char*)(buf32 + loc32++)) = 0;
1975             } else { // Anything but black is opague
1976             *((unsigned char*)(buf32 + loc32++)) = 255;
1977             }
1978             }
1979             }
1980              
1981             // Convert a RGB888 bitmap to a RGB565 bitmap
1982             void c_convert_24_16(char* buf24, unsigned int size24, char* buf16, unsigned char color_order) {
1983             unsigned int loc16 = 0;
1984             unsigned int loc24 = 0;
1985             unsigned short rgb565 = 0;
1986             while(loc24 < size24) {
1987             unsigned char r8 = *(buf24 + loc24++);
1988             unsigned char g8 = *(buf24 + loc24++);
1989             unsigned char b8 = *(buf24 + loc24++);
1990             unsigned char r5 = ( r8 * 249 + 1014 ) >> 11;
1991             unsigned char g6 = ( g8 * 253 + 505 ) >> 10;
1992             unsigned char b5 = ( b8 * 249 + 1014 ) >> 11;
1993             if (color_order == RGB) {
1994             rgb565 = (b5 << 11) | (g6 << 5) | r5;
1995             } else {
1996             rgb565 = (r5 << 11) | (g6 << 5) | b5;
1997             }
1998             *((unsigned short*)(buf16 + loc16)) = rgb565;
1999             loc16 += 2;
2000             }
2001             }
2002              
2003             // Convert a RGB8888 bitmap to a RGB565 bitmap
2004             void c_convert_32_16(char* buf32, unsigned int size32, char* buf16, unsigned char color_order) {
2005             unsigned int loc16 = 0;
2006             unsigned int loc32 = 0;
2007             unsigned short rgb565 = 0;
2008             while(loc32 < size32) {
2009             unsigned int crgb = *((unsigned int*)(buf32 + loc32));
2010             unsigned char r8 = crgb & 255;
2011             unsigned char g8 = (crgb >> 8) & 255;
2012             unsigned char b8 = (crgb >> 16) & 255;
2013             unsigned char r5 = ( r8 * 249 + 1014 ) >> 11;
2014             unsigned char g6 = ( g8 * 253 + 505 ) >> 10;
2015             unsigned char b5 = ( b8 * 249 + 1014 ) >> 11;
2016             if (color_order == RGB) {
2017             rgb565 = (b5 << 11) | (g6 << 5) | r5;
2018             } else {
2019             rgb565 = (r5 << 11) | (g6 << 5) | b5;
2020             }
2021             *((unsigned short*)(buf16 + loc16)) = rgb565;
2022             loc16 += 2;
2023             }
2024             }
2025              
2026             // Convert a RGB8888 bitmap to a RGB888 bitmap
2027             void c_convert_32_24(char* buf32, unsigned int size32, char* buf24, unsigned char color_order) {
2028             unsigned int loc24 = 0;
2029             unsigned int loc32 = 0;
2030             while(loc32 < size32) {
2031             *(buf24 + loc24++) = *(buf32 + loc32++);
2032             *(buf24 + loc24++) = *(buf32 + loc32++);
2033             *(buf24 + loc24++) = *(buf32 + loc32++);
2034             loc32++; // Toss the alpha
2035             }
2036             }
2037              
2038             // Convert a RGB888 bitmap to a RGB8888 bitmap
2039             void c_convert_24_32(char* buf24, unsigned int size24, char* buf32, unsigned char color_order) {
2040             unsigned int loc32 = 0;
2041             unsigned int loc24 = 0;
2042             while(loc24 < size24) {
2043             unsigned char r = *(buf24 + loc24++);
2044             unsigned char g = *(buf24 + loc24++);
2045             unsigned char b = *(buf24 + loc24++);
2046             *((unsigned int*)(buf32 + loc32++)) = r | (g << 8) | (b << 16);
2047             loc32 += 3;
2048             if (r == 0 && g == 0 && b == 0) {
2049             *(buf32 + loc32++) = 0;
2050             } else {
2051             *(buf32 + loc32++) = 255;
2052             }
2053             }
2054             }
2055              
2056             // Convert any type RGB bitmap to a monochrome bitmap of the same type
2057             void c_monochrome(char *pixels, unsigned int size, unsigned char color_order, unsigned char bytes_per_pixel) {
2058             unsigned int idx;
2059             unsigned char r;
2060             unsigned char g;
2061             unsigned char b;
2062             unsigned char m;
2063             unsigned short rgb565;
2064              
2065             for (idx = 0; idx < size; idx += bytes_per_pixel) {
2066             if (bytes_per_pixel >= 3) {
2067             switch(color_order) {
2068             case RBG : // RBG
2069             r = *(pixels + idx);
2070             b = *(pixels + idx + 1);
2071             g = *(pixels + idx + 2);
2072             break;
2073             case BGR : // BGR
2074             b = *(pixels + idx);
2075             g = *(pixels + idx + 1);
2076             r = *(pixels + idx + 2);
2077             break;
2078             case BRG : // BRG
2079             b = *(pixels + idx);
2080             r = *(pixels + idx + 1);
2081             g = *(pixels + idx + 2);
2082             break;
2083             case GBR : // GBR
2084             g = *(pixels + idx);
2085             b = *(pixels + idx + 1);
2086             r = *(pixels + idx + 2);
2087             break;
2088             case GRB : // GRB
2089             g = *(pixels + idx);
2090             r = *(pixels + idx + 1);
2091             b = *(pixels + idx + 2);
2092             break;
2093             default : // RGB
2094             r = *(pixels + idx);
2095             g = *(pixels + idx + 1);
2096             b = *(pixels + idx + 2);
2097             }
2098             } else {
2099             rgb565 = *((unsigned short*)(pixels + idx));
2100             g = (rgb565 >> 6) & 31;
2101             if (color_order == 0) { // RGB
2102             r = rgb565 & 31;
2103             b = (rgb565 >> 11) & 31;
2104             } else { // BGR
2105             b = rgb565 & 31;
2106             r = (rgb565 >> 11) & 31;
2107             }
2108             }
2109             m = (unsigned char) round(0.2126 * r + 0.7152 * g + 0.0722 * b);
2110              
2111             switch(bytes_per_pixel) {
2112             case 4 :
2113             if (m == 0) {
2114             *((unsigned int*)(pixels + idx)) = m | (m << 8) | (m << 16);
2115             } else {
2116             *((unsigned int*)(pixels + idx)) = m | (m << 8) | (m << 16) | 0xFF000000;
2117             }
2118             break;
2119             case 3 :
2120             *(pixels + idx) = m;
2121             *(pixels + idx + 1) = m;
2122             *(pixels + idx + 2) = m;
2123             break;
2124             case 2 :
2125             rgb565 = 0;
2126             rgb565 = (m << 11) | (m << 6) | m;
2127             *((unsigned short*)(pixels + idx)) = rgb565;
2128             break;
2129             }
2130             }
2131             }
2132              
2133             C_CODE
2134              
2135             our @HATCHES = Imager::Fill->hatches;
2136             our @COLORORDER = (qw( RGB RBG BGR BRG GBR GRB ));
2137              
2138             =head1 METHODS
2139              
2140             With the exception of "new" and some other methods that only expect one parameter, the methods expect a single hash reference to be passed. This may seem unusual, but it was chosen for speed, and speed is important in a Perl graphics module.
2141              
2142             =cut
2143              
2144             sub new {
2145             =head2 B
2146              
2147             This instantiates the framebuffer object
2148              
2149             =over 4
2150              
2151             my $fb = Graphics::Framebuffer->new(parameter => value);
2152              
2153             =back
2154              
2155             =head3 PARAMETERS
2156              
2157             =over 6
2158              
2159             =item B
2160              
2161             Framebuffer device name. If this is not defined, then it tries the following devices in the following order:
2162              
2163             * /dev/fb0 - 31
2164             * /dev/graphics/fb0 - 31
2165              
2166             If none of these work, then the module goes into emulation mode.
2167              
2168             You really only need to define this if there is more than one framebuffer device in your system, and you want a specific one (else it always chooses the first it finds). If you have only one framebuffer device, then you likely do not need to define this.
2169              
2170             Use "EMULATED" instead of an actual framebuffer device, and it will open a memory only or "emulated" framebuffer. You can use this mode to have multiple "layers" for loading and manipulating images, but a single main framebuffer for displaying them.
2171              
2172             =item B
2173              
2174             Sets the default (global) foreground color for when 'attribute_reset' is called. It is in the same format as "set_color" expects:
2175              
2176             { # This is the default value
2177             'red' => 255,
2178             'green' => 255,
2179             'blue' => 255,
2180             'alpha' => 255
2181             }
2182              
2183             * Do not use this to change colors, as "set_color" is intended for that. Use this to set the DEFAULT foreground color for when "attribute_reset" is called.
2184              
2185             =item B
2186              
2187             Sets the default (global) background color for when 'attribute_reset' is called. It is in the same format as "set_b_color" expects:
2188              
2189             { # This is the default value
2190             'red' => 0,
2191             'green' => 0,
2192             'blue' => 0,
2193             'alpha' => 0
2194             }
2195              
2196             * Do not use this to change background colors, as "set_b_color" is intended for that. Use this to set the DEFAULT background color for when "attribute_reset" is called.
2197              
2198             =item B
2199              
2200             The splash screen is or is not displayed
2201              
2202             A value other than zero turns on the splash screen, and the value is the wait time to show it (default 2 seconds)
2203             A zero value turns it off
2204              
2205             =item B
2206              
2207             Overrides the default font path for TrueType/Type1 fonts
2208              
2209             If 'ttf_print' is not displaying any text, then this may need to be overridden.
2210              
2211             =item B
2212              
2213             Overrides the default font filename for TrueType/Type 1 fonts.
2214              
2215             If 'ttf_print' is not displaying any text, then this may need to be overridden.
2216              
2217             =item B
2218              
2219             Normally this module is completely silent and does not display errors or warnings (to the best of its ability). This is to prevent corruption of the graphics. However, you can enable error reporting by setting this to 1.
2220              
2221             This is helpful for troubleshooting.
2222              
2223             =item B
2224              
2225             If true, it shows images as they load, and displays benchmark informtion in the loading process.
2226              
2227             =item B [0 or 1 (default)]
2228              
2229             When the object is created, it automatically creates a simple signal handler for B and B to run B as a clean way of exiting your script and restoring the screen to defaults.
2230              
2231             Also, when the object is destroyed, it is assumed you are exiting your script. This causes Graphics::Framebuffer to execute "exec('reset')" as its method of exiting instead of having you use "exit".
2232              
2233             You can disable this behavior by setting this to 0.
2234              
2235             =back
2236              
2237             =head3 EMULATION MODE OPTIONS
2238              
2239             =over 6
2240              
2241             The options here only apply to emulation mode.
2242              
2243             Emulation mode can be used as a secondary off-screen drawing surface, if you are clever.
2244              
2245             =back
2246              
2247             =over 12
2248              
2249             =item B => 'EMULATED'
2250              
2251             Sets this object to be in emulation mode.
2252              
2253             Emulation mode special variables for "new" method:
2254              
2255             =item B
2256              
2257             Width of the emulation framebuffer in pixels. Default is 640.
2258              
2259             =item B
2260              
2261             Height of the emulation framebuffer in pixels. Default is 480.
2262              
2263             =item B
2264              
2265             Number of bits per pixel in the emulation framebuffer. Default is 32.
2266              
2267             =item B
2268              
2269             Number of bytes per pixel in the emulation framebuffer. It's best to keep it BITS/8. Default is 4.
2270              
2271             =item B
2272              
2273             Defines the colorspace for the graphics routines to draw in. The possible (and only accepted) string values are:
2274              
2275             'RGB' for Red-Green-Blue (the default)
2276             'RBG' for Red-Blue-Green
2277             'GRB' for Green-Red-Blue
2278             'GBR' for Green-Blue-Red
2279             'BRG' for Blue-Red-Green
2280             'BGR' for Blue-Green-Red (Many video cards are this)
2281              
2282             Why do many video cards use the BGR color order? Simple, their GPUs operate with the high to low byte order for long words. To the video card, it is RGB, but to a CPU that stores bytes in low to high byte order.
2283              
2284             =back
2285              
2286             ##############################################################################
2287              
2288             =cut
2289              
2290 2     2 0 6001915 my $class = shift;
2291              
2292             # I would have liked to make this a lot more organized, but over the years it
2293             # kind of became this mess. I could change it, but it likely would break any
2294             # code that directly uses values.
2295 2         8 my $this;
2296 2         191 $ENV{'PATH'} = '/usr/bin:/bin:/usr/local/bin'; # Testing doesn't work in taint mode unless this is here.
2297             my $self = {
2298             'SCREEN' => '', # The all mighty framebuffer that is mapped to the real framebuffer later
2299              
2300             'RESET' => TRUE, # Default to use 'reset' on destroy
2301             'VERSION' => $VERSION, # Helps with debugging for people sending me dumps
2302             'HATCHES' => [@HATCHES], # Pull in hatches from Imager
2303              
2304             # Set up the user defined graphics primitives and attributes default values
2305             'Imager-Has-TrueType' => $Imager::formats{'tt'} || 0,
2306             'Imager-Has-Type1' => $Imager::formats{'t1'} || 0,
2307             'Imager-Has-Freetype2' => $Imager::formats{'ft2'} || 0,
2308 2   50     172 'Imager-Image-Types' => [ map( {uc($_) } Imager->read_types()) ],
  14   50     2987  
      50        
2309              
2310             'X' => 0, # Last position plotted X
2311             'Y' => 0, # Last position plotted Y
2312             'X_CLIP' => 0, # Top left clip start X
2313             'Y_CLIP' => 0, # Top left clip start Y
2314             'YY_CLIP' => undef, # Bottom right clip end X
2315             'XX_CLIP' => undef, # Bottom right clip end Y
2316             'CLIPPED' => FALSE, # Indicates if clipping is less than the full screen
2317             'IMAGER_FOREGROUND_COLOR' => undef, # Imager foreground color
2318             'IMAGER_BACKGROUND_COLOR' => undef, # Imager background color
2319             'RAW_FOREGROUND_COLOR' => undef, # Global foreground color (Raw string)
2320             'RAW_BACKGROUND_COLOR' => undef, # Global Background Color
2321             'DRAW_MODE' => NORMAL_MODE, # Drawing mode (Normal default)
2322             'DIAGNOSTICS' => FALSE, # Determines if diagnostics are shown when images are loaded.
2323              
2324             'SHOW_ERRORS' => FALSE, # If on, it will output any errors in Imager or elsewhere, else all errors are squelched
2325              
2326             'FOREGROUND' => { # Default foreground for "attribute_reset" method
2327             'red' => 255,
2328             'green' => 255,
2329             'blue' => 255,
2330             'alpha' => 255
2331             },
2332             'BACKGROUND' => { # Default background for "attribute_reset" method
2333             'red' => 0,
2334             'green' => 0,
2335             'blue' => 0,
2336             'alpha' => 0
2337             },
2338              
2339             'FONT_PATH' => '/usr/share/fonts/truetype/freefont', # Default fonts path
2340             'FONT_FACE' => 'FreeSans.ttf', # Default font face
2341              
2342             'SPLASH' => 2, # Time in seconds to show the splash screen
2343              
2344             'WAIT_FOR_CONSOLE' => FALSE,
2345             'THIS_CONSOLE' => 1,
2346             'CONSOLE' => 1,
2347              
2348             'NORMAL_MODE' => NORMAL_MODE, # Constants for DRAW_MODE
2349             'XOR_MODE' => XOR_MODE,
2350             'OR_MODE' => OR_MODE,
2351             'AND_MODE' => AND_MODE,
2352             'MASK_MODE' => MASK_MODE,
2353             'UNMASK_MODE' => UNMASK_MODE,
2354             'ALPHA_MODE' => ALPHA_MODE,
2355             'ADD_MODE' => ADD_MODE,
2356             'SUBTRACT_MODE' => SUBTRACT_MODE,
2357             'MULTIPLY_MODE' => MULTIPLY_MODE,
2358             'DIVIDE_MODE' => DIVIDE_MODE,
2359              
2360             'ARC' => ARC, # Constants for "draw_arc" method
2361             'PIE' => PIE,
2362             'POLY_ARC' => POLY_ARC,
2363              
2364             'RGB' => RGB, # Constants for color mapping
2365             'RBG' => RBG, # Constants for color mapping
2366             'BGR' => BGR, # Constants for color mapping
2367             'BRG' => BRG, # Constants for color mapping
2368             'GBR' => GBR, # Constants for color mapping
2369             'GRB' => GRB, # Constants for color mapping
2370              
2371             'CENTER_NONE' => CENTER_NONE, # Constants for centering
2372             'CENTER_X' => CENTER_X, # Constants for centering
2373             'CENTER_Y' => CENTER_Y, # Constants for centering
2374             'CENTER_XY' => CENTER_XY, # Constants for centering
2375             'CENTRE_NONE' => CENTRE_NONE, # Constants for centering
2376             'CENTRE_X' => CENTRE_X, # Constants for centering
2377             'CENTRE_Y' => CENTRE_Y, # Constants for centering
2378             'CENTRE_XY' => CENTRE_XY, # Constants for centering
2379             ####################################################################
2380              
2381             'KD_GRAPHICS' => 1,
2382             'KD_TEXT' => 0,
2383              
2384             # I=32.64,L=32,S=16,C=8,A=string
2385             # Structure Definitions
2386             'vt_stat' => 'SSS', # v_active, v_signal, v_state
2387             'FBioget_vscreeninfo' => 'L' . # 32 bits for xres
2388             'L' . # 32 bits for yres
2389             'L' . # 32 bits for xres_virtual
2390             'L' . # 32 bits for yres_vortual
2391             'L' . # 32 bits for xoffset
2392             'L' . # 32 bits for yoffset
2393             'L' . # 32 bits for bits per pixel
2394             'L' . # 32 bits for grayscale (0=color)
2395             'L' . # 32 bits for red bit offset
2396             'L' . # 32 bits for red bit length
2397             'L' . # 32 bits for red msb_right (!0 msb is right)
2398             'L' . # 32 bits for green bit offset
2399             'L' . # 32 bits for green bit length
2400             'L' . # 32 bits for green msb_right (!0 msb is right)
2401             'L' . # 32 bits for blue bit offset
2402             'L' . # 32 bits for blue bit length
2403             'L' . # 32 bits for blue msb_right (!0 msb is right)
2404             'L' . # 32 bits for alpha bit offset
2405             'L' . # 32 bits for alpha bit length
2406             'L' . # 32 bits for alpha msb_right (!0 msb is right)
2407             'L' . # 32 bits for nonstd (!0 non standard pixel format)
2408             'L' . # 32 bits for activate
2409             'L' . # 32 bits for height in mm
2410             'L' . # 32 bits for width in mm
2411             'L' . # 32 bits for accel_flags (obsolete)
2412             'L' . # 32 bits for pixclock
2413             'L' . # 32 bits for left margin
2414             'L' . # 32 bits for right margin
2415             'L' . # 32 bits for upper margin
2416             'L' . # 32 bits for lower margin
2417             'L' . # 32 bits for hsync length
2418             'L' . # 32 bits for vsync length
2419             'L' . # 32 bits for sync
2420             'L' . # 32 bits for vmode
2421             'L' . # 32 bits for rotate (angle we rotate counter clockwise)
2422             'L' . # 32 bits for colorspace
2423             'L4', # 32 bits x 4 reserved
2424              
2425             'FBioget_fscreeninfo' => 'A16' . # 16 bytes for ID name
2426             'I' . # 32/64 bits unsigned address
2427             'L' . # 32 bits for smem_len
2428             'L' . # 32 bits for type
2429             'L' . # 32 bits for type_aux (interleave)
2430             'L' . # 32 bits for visual
2431             'S' . # 16 bits for xpanstep
2432             'S' . # 16 bits for ypanstep
2433             'S1' . # 16 bits for ywrapstep (extra 16 bits added on if system is 8 byte aligned)
2434             'L' . # 32 bits for line length in bytes
2435             'I' . # 32/64 bits for mmio_start
2436             'L' . # 32 bits for mmio_len
2437             'L' . # 32 bits for accel
2438             'S' . # 16 bits for capabilities
2439             'S2', # 16 bits x 2 reserved
2440              
2441             # Default values
2442             'GARBAGE' => FALSE,
2443             'VXRES' => 640, # Virtual X resolution
2444             'VYRES' => 480, # Virtual Y resolution
2445             'BITS' => 32, # Bits per pixel
2446             'BYTES' => 4, # Bytes per pixel
2447             'XOFFSET' => 0, # Visible screen X offset
2448             'YOFFSET' => 0, # Visible screen Y offset
2449             'FB_DEVICE' => undef, # Framebuffer device name (defined later)
2450             'COLOR_ORDER' => 'RGB', # Default color Order. Redefined later to be an integer
2451             'ACCELERATED' => SOFTWARE, # Use accelerated graphics
2452             # 0 = Pure Perl
2453             # 1 = C Accelerated (but still software)
2454             # 2 = C & Hardware accelerated.
2455             'FBIO_WAITFORVSYNC' => 0x4620,
2456             'VT_GETSTATE' => 0x5603,
2457             'KDSETMODE' => 0x4B3A,
2458             'FBIOGET_VSCREENINFO' => 0x4600, # These come from "fb.h" in the kernel source
2459             'FBIOGET_FSCREENINFO' => 0x4602,
2460             @_ # Pull in the overrides
2461             };
2462 2 50       18 if ($self->{'GARBAGE'}) {
2463 0         0 my $garbage = {
2464              
2465             'PIXEL_TYPES' => [
2466             'Packed Pixels',
2467             'Planes',
2468             'Interleaved Planes',
2469             'Text',
2470             'VGA Planes',
2471             ],
2472             'PIXEL_TYPES_AUX' => {
2473             'Packed Pixels' => [
2474             '',
2475             ],
2476             'Planes' => [
2477             '',
2478             ],
2479             'Interleaved Planes' => [
2480             '',
2481             ],
2482             'Text' => [
2483             'MDA',
2484             'CGA',
2485             'S3 MMIO',
2486             'MGA Step 16',
2487             'MGA Step 8',
2488             'SVGA Group',
2489             'SVGA Mask',
2490             'SVGA Step 2',
2491             'SVGA Step 4',
2492             'SVGA Step 8',
2493             'SVGA Step 16',
2494             'SVGA Last',
2495             ],
2496             'VGA Planes' => [
2497             'VGA 4',
2498             'CFB 4',
2499             'CFB 8',
2500             ],
2501             },
2502             'VISUAL_TYPES' => [
2503             'Mono 01',
2504             'Mono 10',
2505             'True Color',
2506             'Pseudo Color',
2507             'Direct Color',
2508             'Static Pseudo Color',
2509             ],
2510             'ACCEL_TYPES' => [
2511             'NONE',
2512             'Atari Blitter',
2513             'Amiga Blitter',
2514             'S3 Trio64',
2515             'NCR 77C32BLT',
2516             'S3 Virge',
2517             'ATI Mach 64 GX',
2518             'ATI DEC TGA',
2519             'ATI Mach 64 CT',
2520             'ATI Mach 64 VT',
2521             'ATI Mach 64 GT',
2522             'Sun Creator',
2523             'Sun CG Six',
2524             'Sun Leo',
2525             'IWS Twin Turbo',
2526             '3D Labs Permedia2',
2527             'Matrox MGA 2064W',
2528             'Matrox MGA 1064SG',
2529             'Matrox MGA 2164W',
2530             'Matrox MGA 2164W AGP',
2531             'Matrox MGA G100',
2532             'Matrox MGA G200',
2533             'Sun CG14',
2534             'Sun BW Two',
2535             'Sun CG Three',
2536             'Sun TCX',
2537             'Matrox MGA G400',
2538             'NV3',
2539             'NV4',
2540             'NV5',
2541             'CT 6555x',
2542             '3DFx Banshee',
2543             'ATI Rage 128',
2544             'IGS Cyber 2000',
2545             'IGS Cyber 2010',
2546             'IGS Cyber 5000',
2547             'SIS Glamour',
2548             '3D Labs Permedia',
2549             'ATI Radeon',
2550             'i810',
2551             'NV 10',
2552             'NV 20',
2553             'NV 30',
2554             'NV 40',
2555             'XGI Volari V',
2556             'XGI Volari Z',
2557             'OMAP i610',
2558             'Trident TGUI',
2559             'Trident 3D Image',
2560             'Trident Blade 3D',
2561             'Trident Blade XP',
2562             'Cirrus Alpine',
2563             'Neomagic NM2070',
2564             'Neomagic NM2090',
2565             'Neomagic NM2093',
2566             'Neomagic NM2097',
2567             'Neomagic NM2160',
2568             'Neomagic NM2200',
2569             'Neomagic NM2230',
2570             'Neomagic NM2360',
2571             'Neomagic NM2380',
2572             'PXA3XX', # 99
2573             '','','','','','','','','','','','','','','','','','','','','','','','','','','','',
2574             'Savage 4',
2575             'Savage 3D',
2576             'Savage 3D MV',
2577             'Savage 2000',
2578             'Savage MX MV',
2579             'Savage MX',
2580             'Savage IX MV',
2581             'Savage IX',
2582             'Pro Savage PM',
2583             'Pro Savage KM',
2584             'S3 Twister P',
2585             'S3 Twister K',
2586             'Super Savage',
2587             'Pro Savage DDR',
2588             'Pro Savage DDRX',
2589             ],
2590             # Unfortunately, these are not IOCTLs. Gee, that would be nice if they were.
2591             'FBinfo_hwaccel_fillrect' => 'L6', # dx(32),dy(32),width(32),height(32),color(32),rop(32)?
2592             'FBinfo_hwaccel_copyarea' => 'L6', # dx(32),dy(32),width(32),height(32),sx(32),sy(32)
2593             'FBinfo_hwaccel_fillrect' => 'L6', # dx(32),dy(32),width(32),height(32),color(32),rop(32)
2594             'FBinfo_hwaccel_imageblit' => 'L6CL', # dx(32),dy(32),width(32),height(32),fg_color(32),bg_color(32),depth(8),image pointer(32),color map pointer(32)
2595             # COLOR MAP:
2596             # start(32),length(32),red(16),green(16),blue(16),alpha(16)
2597             # FLAGS
2598             'FBINFO_HWACCEL_NONE' => 0x0000, # These come from "fb.h" in the kernel source
2599             'FBINFO_HWACCEL_COPYAREA' => 0x0100,
2600             'FBINFO_HWACCEL_FILLRECT' => 0x0200,
2601             'FBINFO_HWACCEL_IMAGEBLIT' => 0x0400,
2602             'FBINFO_HWACCEL_ROTATE' => 0x0800,
2603             'FBINFO_HWACCEL_XPAN' => 0x1000,
2604             'FBINFO_HWACCEL_YPAN' => 0x2000,
2605             'FBINFO_HWACCEL_YWRAP' => 0x4000,
2606              
2607             ## Set up the Framebuffer driver "constants" defaults
2608             # These "fb.h" constants may go away in future versions, as the data needed to get from these
2609             # Is available from Inline::C now.
2610             # Commands
2611             'FBIOPUT_VSCREENINFO' => 0x4601,
2612             'FBIOGETCMAP' => 0x4604,
2613             'FBIOPUTCMAP' => 0x4605,
2614             'FBIOPAN_DISPLAY' => 0x4606,
2615             'FBIO_CURSOR' => 0x4608,
2616             'FBIOGET_CON2FBMAP' => 0x460F,
2617             'FBIOPUT_CON2FBMAP' => 0x4610,
2618             'FBIOBLANK' => 0x4611,
2619             'FBIOGET_VBLANK' => 0x4612,
2620             'FBIOGET_GLYPH' => 0x4615,
2621             'FBIOGET_HWCINFO' => 0x4616,
2622             'FBIOPUT_MODEINFO' => 0x4617,
2623             'FBIOGET_DISPINFO' => 0x4618,
2624             };
2625 0         0 $self = { %{$self},%{$garbage} };
  0         0  
  0         0  
2626             }
2627 2 50       10 unless (defined($self->{'FB_DEVICE'})) { # We scan for all 32 possible devices at both possible locations
2628 2         18 foreach my $dev (0 .. 31) {
2629 64         103 foreach my $prefix (qw(/dev/fb /dev/fb/ /dev/graphics/fb)) {
2630 192 50       2036 if (-e "$prefix$dev") {
2631 0         0 $self->{'FB_DEVICE'} = "$prefix$dev";
2632 0         0 last;
2633             }
2634             }
2635 64 50       184 last if (defined($self->{'FB_DEVICE'}));
2636             }
2637             }
2638 2         16 $self->{'CONSOLE'} = 1;
2639 2         6 eval {
2640 2         25 $self->{'CONSOLE'} = _slurp('/sys/class/tty/tty0/active');
2641 2         25 $self->{'CONSOLE'} =~ s/\D+//gs;
2642 2         10 $self->{'CONSOLE'} += 0;
2643 2         9 $self->{'THIS_CONSOLE'} = $self->{'CONSOLE'};
2644             };
2645 2 50 33     45 if ( ! defined($ENV{'DISPLAY'}) && defined($self->{'FB_DEVICE'}) && (-e $self->{'FB_DEVICE'}) && open($self->{'FB'}, '+<', $self->{'FB_DEVICE'})) { # Can we open the framebuffer device??
    50 33        
      33        
      33        
2646 0         0 binmode($self->{'FB'}); # We have to be in binary mode first
2647 0         0 $|++;
2648 0 0       0 if ($self->{'ACCELERATED'}) {
2649             (
2650             $self->{'fscreeninfo'}->{'id'},
2651             $self->{'fscreeninfo'}->{'smem_start'},
2652             $self->{'fscreeninfo'}->{'smem_len'},
2653             $self->{'fscreeninfo'}->{'type'},
2654             $self->{'fscreeninfo'}->{'type_aux'},
2655             $self->{'fscreeninfo'}->{'visual'},
2656             $self->{'fscreeninfo'}->{'xpanstep'},
2657             $self->{'fscreeninfo'}->{'ypanstep'},
2658             $self->{'fscreeninfo'}->{'ywrapstep'},
2659             $self->{'fscreeninfo'}->{'line_length'},
2660             $self->{'fscreeninfo'}->{'mmio_start'},
2661             $self->{'fscreeninfo'}->{'mmio_len'},
2662             $self->{'fscreeninfo'}->{'accel'},
2663              
2664             $self->{'vscreeninfo'}->{'xres'},
2665             $self->{'vscreeninfo'}->{'yres'},
2666             $self->{'vscreeninfo'}->{'xres_virtual'},
2667             $self->{'vscreeninfo'}->{'yres_virtual'},
2668             $self->{'vscreeninfo'}->{'xoffset'},
2669             $self->{'vscreeninfo'}->{'yoffset'},
2670             $self->{'vscreeninfo'}->{'bits_per_pixel'},
2671             $self->{'vscreeninfo'}->{'grayscale'},
2672             $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'},
2673             $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'},
2674             $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'msb_right'},
2675             $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'},
2676             $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'},
2677             $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'msb_right'},
2678             $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'},
2679             $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'},
2680             $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'msb_right'},
2681             $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'},
2682             $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'length'},
2683             $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'msb_right'},
2684             $self->{'vscreeninfo'}->{'nonstd'},
2685             $self->{'vscreeninfo'}->{'activate'},
2686             $self->{'vscreeninfo'}->{'height'},
2687             $self->{'vscreeninfo'}->{'width'},
2688             $self->{'vscreeninfo'}->{'accel_flags'},
2689             $self->{'vscreeninfo'}->{'pixclock'},
2690             $self->{'vscreeninfo'}->{'left_margin'},
2691             $self->{'vscreeninfo'}->{'right_margin'},
2692             $self->{'vscreeninfo'}->{'upper_margin'},
2693             $self->{'vscreeninfo'}->{'lower_margin'},
2694             $self->{'vscreeninfo'}->{'hsync_len'},
2695             $self->{'vscreeninfo'}->{'vsync_len'},
2696             $self->{'vscreeninfo'}->{'sync'},
2697             $self->{'vscreeninfo'}->{'vmode'},
2698             $self->{'vscreeninfo'}->{'rotate'},
2699 0         0 ) = (c_get_screen_info($self->{'FB_DEVICE'}));
2700             } else { # Fallback if not accelerated. Do it the old way
2701             # Make the IOCTL call to get info on the virtual (viewable) screen (Sometimes different than physical)
2702             (
2703             $self->{'vscreeninfo'}->{'xres'}, # (32)
2704             $self->{'vscreeninfo'}->{'yres'}, # (32)
2705             $self->{'vscreeninfo'}->{'xres_virtual'}, # (32)
2706             $self->{'vscreeninfo'}->{'yres_virtual'}, # (32)
2707             $self->{'vscreeninfo'}->{'xoffset'}, # (32)
2708             $self->{'vscreeninfo'}->{'yoffset'}, # (32)
2709             $self->{'vscreeninfo'}->{'bits_per_pixel'}, # (32)
2710             $self->{'vscreeninfo'}->{'grayscale'}, # (32)
2711             $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}, # (32)
2712             $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'}, # (32)
2713             $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'msb_right'}, # (32)
2714             $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}, # (32)
2715             $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'}, # (32)
2716             $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'msb_right'}, # (32)
2717             $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}, # (32)
2718             $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'}, # (32)
2719             $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'msb_right'}, # (32)
2720             $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'}, # (32)
2721             $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'length'}, # (32)
2722             $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'msb_right'}, # (32)
2723             $self->{'vscreeninfo'}->{'nonstd'}, # (32)
2724             $self->{'vscreeninfo'}->{'activate'}, # (32)
2725             $self->{'vscreeninfo'}->{'height'}, # (32)
2726             $self->{'vscreeninfo'}->{'width'}, # (32)
2727             $self->{'vscreeninfo'}->{'accel_flags'}, # (32)
2728             $self->{'vscreeninfo'}->{'pixclock'}, # (32)
2729             $self->{'vscreeninfo'}->{'left_margin'}, # (32)
2730             $self->{'vscreeninfo'}->{'right_margin'}, # (32)
2731             $self->{'vscreeninfo'}->{'upper_margin'}, # (32)
2732             $self->{'vscreeninfo'}->{'lower_margin'}, # (32)
2733             $self->{'vscreeninfo'}->{'hsync_len'}, # (32)
2734             $self->{'vscreeninfo'}->{'vsync_len'}, # (32)
2735             $self->{'vscreeninfo'}->{'sync'}, # (32)
2736             $self->{'vscreeninfo'}->{'vmode'}, # (32)
2737             $self->{'vscreeninfo'}->{'rotate'}, # (32)
2738             $self->{'vscreeninfo'}->{'colorspace'}, # (32)
2739 0         0 @{ $self->{'vscreeninfo'}->{'reserved_fb_vir'} } # (32) x 4
2740 0         0 ) = _get_ioctl(FBIOGET_VSCREENINFO, $self->{'FBioget_vscreeninfo'}, $self->{'FB'});
2741             # Make the IOCTL call to get info on the physical screen
2742 0         0 my $extra = 1;
2743             do { # A hacked way to do this, but it seems to work
2744 0         0 my $typedef = '' . $self->{'FBioget_fscreeninfo'};
2745 0 0       0 if ($extra > 1) { # It turns out it was byte alignment issues, not driver weirdness
2746 0 0       0 if ($extra == 2) {
    0          
    0          
2747 0         0 $typedef =~ s/S1/S$extra/;
2748             } elsif ($extra == 3) {
2749 0         0 $typedef =~ s/S1/L/;
2750             } elsif ($extra == 4) {
2751 0         0 $typedef =~ s/S1/I/;
2752             }
2753             (
2754             $self->{'fscreeninfo'}->{'id'}, # (8) x 16
2755             $self->{'fscreeninfo'}->{'smem_start'}, # LONG
2756             $self->{'fscreeninfo'}->{'smem_len'}, # (32)
2757             $self->{'fscreeninfo'}->{'type'}, # (32)
2758             $self->{'fscreeninfo'}->{'type_aux'}, # (32)
2759             $self->{'fscreeninfo'}->{'visual'}, # (32)
2760             $self->{'fscreeninfo'}->{'xpanstep'}, # (16)
2761             $self->{'fscreeninfo'}->{'ypanstep'}, # (16)
2762             $self->{'fscreeninfo'}->{'ywrapstep'}, # (16)
2763             $self->{'fscreeninfo'}->{'filler'}, # (16) - Just a filler
2764             $self->{'fscreeninfo'}->{'line_length'}, # (32)
2765             $self->{'fscreeninfo'}->{'mmio_start'}, # LONG
2766             $self->{'fscreeninfo'}->{'mmio_len'}, # (32)
2767             $self->{'fscreeninfo'}->{'accel'}, # (32)
2768             $self->{'fscreeninfo'}->{'capailities'}, # (16)
2769 0         0 @{ $self->{'fscreeninfo'}->{'reserved_fb_phys'} } # (16) x 2
2770 0         0 ) = _get_ioctl(FBIOGET_FSCREENINFO, $typedef, $self->{'FB'});
2771             } else {
2772             (
2773             $self->{'fscreeninfo'}->{'id'}, # (8) x 16
2774             $self->{'fscreeninfo'}->{'smem_start'}, # LONG
2775             $self->{'fscreeninfo'}->{'smem_len'}, # (32)
2776             $self->{'fscreeninfo'}->{'type'}, # (32)
2777             $self->{'fscreeninfo'}->{'type_aux'}, # (32)
2778             $self->{'fscreeninfo'}->{'visual'}, # (32)
2779             $self->{'fscreeninfo'}->{'xpanstep'}, # (16)
2780             $self->{'fscreeninfo'}->{'ypanstep'}, # (16)
2781             $self->{'fscreeninfo'}->{'ywrapstep'}, # (16)
2782             $self->{'fscreeninfo'}->{'line_length'}, # (32)
2783             $self->{'fscreeninfo'}->{'mmio_start'}, # LONG
2784             $self->{'fscreeninfo'}->{'mmio_len'}, # (32)
2785             $self->{'fscreeninfo'}->{'accel'}, # (32)
2786             $self->{'fscreeninfo'}->{'capailities'}, # (16)
2787 0         0 @{ $self->{'fscreeninfo'}->{'reserved_fb_phys'} } # (16) x 2
2788 0         0 ) = _get_ioctl(FBIOGET_FSCREENINFO, $typedef, $self->{'FB'});
2789             }
2790              
2791 0         0 $extra++;
2792 0   0     0 } until (($self->{'fscreeninfo'}->{'line_length'} < $self->{'fscreeninfo'}->{'smem_len'} && $self->{'fscreeninfo'}->{'line_length'} > 0) || $extra > 4);
      0        
2793             }
2794 0         0 $self->{'fscreeninfo'}->{'id'} =~ s/[\x00-\x1F,\x7F-\xFF]//gs;
2795 0 0       0 if ($self->{'fscreeninfo'}->{'id'} eq '') {
2796 0         0 chomp(my $model = `cat /proc/device-tree/model`);
2797 0         0 $model =~ s/[\x00-\x1F,\x7F-\xFF]//gs;
2798 0 0       0 if ($model ne '') {
2799 0         0 $self->{'fscreeninfo'}->{'id'} = $model;
2800             } else {
2801 0         0 $self->{'fscreeninfo'}->{'id'} = $self->{'FB_DEVICE'};
2802             }
2803             }
2804              
2805 0         0 $self->{'GPU'} = $self->{'fscreeninfo'}->{'id'}; # The name of the GPU or video driver
2806 0         0 $self->{'VXRES'} = $self->{'vscreeninfo'}->{'xres_virtual'}; # The virtual width of the screen
2807 0         0 $self->{'VYRES'} = $self->{'vscreeninfo'}->{'yres_virtual'}; # The virtual height of the screen
2808 0         0 $self->{'XRES'} = $self->{'vscreeninfo'}->{'xres'}; # The physical width of the screen
2809 0         0 $self->{'YRES'} = $self->{'vscreeninfo'}->{'yres'}; # The physical height of the screen
2810 0   0     0 $self->{'XOFFSET'} = $self->{'vscreeninfo'}->{'xoffset'} || 0; # The horizontal offset of the screen from the beginning of the virtual screen
2811 0   0     0 $self->{'YOFFSET'} = $self->{'vscreeninfo'}->{'yoffset'} || 0; # The vertical offset of the screen from the beginning of the virtual screen
2812 0         0 $self->{'BITS'} = $self->{'vscreeninfo'}->{'bits_per_pixel'}; # The bits per pixel of the screen
2813 0         0 $self->{'BYTES'} = $self->{'BITS'} / 8; # The number of bytes per pixel
2814 0         0 $self->{'BYTES_PER_LINE'} = $self->{'fscreeninfo'}->{'line_length'}; # The length of a single scan line in bytes
2815 0         0 $self->{'PIXELS'} = (($self->{'XOFFSET'} + $self->{'VXRES'}) * ($self->{'YOFFSET'} + $self->{'VYRES'}));
2816 0         0 $self->{'SIZE'} = $self->{'PIXELS'} * $self->{'BYTES'};
2817 0 0 0     0 $self->{'fscreeninfo'}->{'smem_len'} = $self->{'BYTES_PER_LINE'} * $self->{'VYRES'} if (!defined($self->{'fscreeninfo'}->{'smem_len'}) || $self->{'fscreeninfo'}->{'smem_len'} <= 0);
2818              
2819 0         0 $self->{'fscreeninfo'}->{'type'} = $self->{'PIXEL_TYPES'}->[$self->{'fscreeninfo'}->{'type'}];
2820 0         0 $self->{'fscreeninfo'}->{'type_aux'} = $self->{'PIXEL_TYPES_AUX'}->{$self->{'fscreeninfo'}->{'type'}}->[$self->{'fscreeninfo'}->{'type_aux'}];
2821 0         0 $self->{'fscreeninfo'}->{'visual'} = $self->{'VISUAL_TYPES'}->[$self->{'fscreeninfo'}->{'visual'}];
2822 0         0 $self->{'fscreeninfo'}->{'accel'} = $self->{'ACCEL_TYPES'}->[$self->{'fscreeninfo'}->{'accel'}];
2823              
2824 0 0 0     0 if ($self->{'BITS'} == 32 && $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'length'} == 0) {
2825             # The video driver doesn't use the alpha channel, but we do, so force it.
2826 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'length'} = 8;
2827 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2828             }
2829             ## For debugging only
2830             # print Dumper($self,\%Config),"\n"; exit;
2831              
2832             # Only useful for debugging and for troubleshooting the module for specific display resolutions
2833 0 0       0 if (defined($self->{'SIMULATED_X'})) {
2834 0         0 my $w = $self->{'XRES'};
2835 0         0 $self->{'XRES'} = $self->{'SIMULATED_X'};
2836 0         0 $self->{'XOFFSET'} += ($w - $self->{'SIMULATED_X'}) / 2;
2837             }
2838 0 0       0 if (defined($self->{'SIMULATED_Y'})) {
2839 0         0 my $h = $self->{'YRES'};
2840 0         0 $self->{'YRES'} = $self->{'SIMULATED_Y'};
2841 0         0 $self->{'YOFFSET'} += ($h - $self->{'SIMULATED_Y'}) / 2;
2842             }
2843 0         0 bless($self, $class);
2844 0         0 $self->_color_order(); # Automatically determine color mode
2845 0         0 $self->attribute_reset();
2846              
2847             # Now that everything is set up, let's map the framebuffer to SCREEN
2848              
2849 0         0 eval { # We use the more stable File::Map now
2850             $self->{'SCREEN_ADDRESS'} = map_handle(
2851             $self->{'SCREEN'},
2852             $self->{'FB'},
2853             '+<',
2854             0,
2855 0         0 $self->{'fscreeninfo'}->{'smem_len'},
2856             );
2857             };
2858 0 0       0 if ($@) {
2859 0         0 print STDERR qq{
2860             OUCH! Graphics::Framebuffer cannot memory map the framebuffer!
2861              
2862             This is usually caused by one or more of the following:
2863              
2864             * Your account does not have proper permission to access the framebuffer
2865             device.
2866              
2867             This usually requires adding the "video" group to your account. This is
2868             usually accomplished via the following command (replace "username" with
2869             your actual username):
2870              
2871             \tsudo usermod -a -G video username
2872              
2873             * You could be attempting to run this inside X-Windows, which doesn't work.
2874             You MUST run your script outside of X-Windows from the system Console.
2875             If you are inside X-Windows, and you do not know how to get to your
2876             console, just hit CTRL-ALT-F2 to access one of the consoles. This has
2877             no windows or mouse functionality. It is command line only (similar to
2878             old DOS).
2879              
2880             To get back into X-Windows, you just hit ALT-F7 (or ALT-F8 on some
2881             systems).
2882              
2883             Actual error reported:\n\n$@\n};
2884 0 0       0 sleep ($self->{'RESET'}) ? 10 : 1;
2885 0         0 exit(1);
2886             }
2887             } elsif (exists($ENV{'DISPLAY'}) && (-e $self->{'FB_DEVICE'})) {
2888 0         0 print STDERR qq{
2889             OUCH! Graphics::Framebuffer cannot memory map the framebuffer!
2890              
2891             You are attempting to run this inside X-Windows, which doesn't work. You MUST
2892             run your script outside of X-Windows from the system Console. If you are
2893             inside X-Windows, and you do not know how to get to your console, just hit
2894             CTRL-ALT-F2 to access one of the consoles. This has no windows or mouse
2895             functionality. It is command line only (similar to old DOS).
2896              
2897             To get back into X-Windows, you just hit ALT-F7 (or ALT-F8 on some systems).
2898             };
2899 0 0       0 sleep ($self->{'RESET'}) ? 10 : 1;
2900 0         0 exit(1);
2901             } else { # Go into emulation mode if no actual framebuffer available
2902 2         8 $self->{'FB_DEVICE'} = 'EMULATED';
2903 2         6 $self->{'SPLASH'} = FALSE;
2904 2         5 $self->{'RESET'} = FALSE;
2905 2         6 $self->{'ERROR'} = 'Framebuffer Device Not Found! Emulation mode. EXPERIMENTAL!!';
2906 2         14 $self->{'COLOR_ORDER'} = $self->{ uc($self->{'COLOR_ORDER'}) }; # Translate the color order
2907              
2908 2         11 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'} = 8;
2909 2         7 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'msb_right'} = 0;
2910 2         7 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'} = 8;
2911 2         6 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'msb_right'} = 0;
2912 2         7 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'} = 8;
2913 2         6 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'msb_right'} = 0;
2914 2         7 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'length'} = 8;
2915 2         4 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'msb_right'} = 0;
2916              
2917 2 50       13 if ($self->{'COLOR_ORDER'} == BGR) {
    50          
    0          
    0          
    0          
    0          
2918 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'} = 16;
2919 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'} = 8;
2920 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'} = 0;
2921 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2922             } elsif ($self->{'COLOR_ORDER'} == RGB) {
2923 2         9 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'} = 0;
2924 2         5 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'} = 8;
2925 2         6 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'} = 16;
2926 2         5 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2927             } elsif ($self->{'COLOR_ORDER'} == BRG) {
2928 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'} = 8;
2929 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'} = 16;
2930 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'} = 0;
2931 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2932             } elsif ($self->{'COLOR_ORDER'} == RBG) {
2933 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'} = 0;
2934 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'} = 16;
2935 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'} = 8;
2936 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2937             } elsif ($self->{'COLOR_ORDER'} == GRB) {
2938 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'} = 8;
2939 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'} = 0;
2940 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'} = 16;
2941 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2942             } elsif ($self->{'COLOR_ORDER'} == GBR) {
2943 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'} = 16;
2944 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'} = 0;
2945 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'} = 8;
2946 0         0 $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'} = 24;
2947             }
2948              
2949             # Set the resolution. Either the defaults, or whatever the user passed in.
2950              
2951 2         2644 $self->{'SCREEN'} = chr(0) x ($self->{'VXRES'} * $self->{'VYRES'} * $self->{'BYTES'}); # This is the fake framebuffer
2952 2         14 $self->{'XRES'} = $self->{'VXRES'}; # Virtual and physical are the same
2953 2         6 $self->{'YRES'} = $self->{'VYRES'};
2954 2         4 $self->{'XOFFSET'} = 0;
2955 2         29 $self->{'YOFFSET'} = 0;
2956 2         12 $self->{'PIXELS'} = (($self->{'XOFFSET'} + $self->{'VXRES'}) * ($self->{'YOFFSET'} + $self->{'VYRES'}));
2957 2         10 $self->{'SIZE'} = $self->{'PIXELS'} * $self->{'BYTES'};
2958 2         9 $self->{'fscreeninfo'}->{'id'} = 'Virtual Framebuffer';
2959 2         8 $self->{'GPU'} = $self->{'fscreeninfo'}->{'id'};
2960 2 50 33     18 $self->{'fscreeninfo'}->{'smem_len'} = $self->{'BYTES'} * ($self->{'VXRES'} * $self->{'VYRES'}) if (!defined($self->{'fscreeninfo'}->{'smem_len'}) || $self->{'fscreeninfo'}->{'smem_len'} <= 0);
2961 2         44 $self->{'BYTES_PER_LINE'} = int($self->{'fscreeninfo'}->{'smem_len'} / $self->{'VYRES'});
2962              
2963 2         18 bless($self, $class);
2964             }
2965 2 50       23 if ($self->{'RESET'}) {
2966 0         0 $SIG{'QUIT'} = $SIG{'INT'} = $SIG{'KILL'} = \&_reset;
2967             }
2968 2         24 $self->_gather_fonts('/usr/share/fonts');
2969             # Loop and find the default font. One of these should work for Debian and Redhat variants.
2970 2         8 foreach my $font (qw(FreeSans Ubuntu-R Arial Oxygen-Sans Garuda LiberationSans-Regular Loma Helvetica)) {
2971 16 50       36 if (exists($self->{'FONTS'}->{$font})) {
2972 0         0 $self->{'FONT_PATH'} = $self->{'FONTS'}->{$font}->{'path'};
2973 0         0 $self->{'FONT_FACE'} = $self->{'FONTS'}->{$font}->{'font'};
2974 0         0 last;
2975             }
2976             }
2977 2         15 $self->_flush_screen();
2978 2         10335 chomp($self->{'this_tty'} = `tty`);
2979 2 50       91 if ($self->{'SPLASH'} > 0) {
2980 0         0 $self->splash($VERSION);
2981 0         0 sleep $self->{'SPLASH'};
2982             }
2983 2         75 $self->attribute_reset();
2984 2 50       13 if (wantarray) { # For the temporarily supported (but no longer) double buffering mode
2985 0         0 return ($self, $self); # For those that coded for double buffering
2986             }
2987 2         54 return ($self);
2988             }
2989              
2990             sub _reset {
2991 0     0   0 system('reset');
2992             }
2993              
2994             sub _fix_mapping { # File::Map SHOULD make this obsolete
2995             # Fixes the mapping if Perl garbage collects (naughty Perl)
2996 0     0   0 my $self = shift;
2997 0         0 unmap($self->{'SCREEN'}); # Unmap missing on some File::Maps
2998 0 0       0 unless (defined($self->{'FB'})) {
2999 0         0 eval { close($self->{'FB'}); };
  0         0  
3000 0         0 open($self->{'FB'}, '+<', $self->{'FB_DEVICE'});
3001 0         0 binmode($self->{'FB'});
3002 0         0 $self->_flush_screen();
3003             }
3004 0         0 $self->{'MAP_ATTEMPTS'}++;
3005             # We don't eval, because it worked originally
3006 0         0 $self->{'SCREEN_ADDRESS'} = map_handle($self->{'SCREEN'}, $self->{'FB'}, '+<', 0, $self->{'fscreeninfo'}->{'smem_len'});
3007             }
3008              
3009             sub _color_order {
3010             # Determine the color order the video card uses
3011 0     0   0 my $self = shift;
3012              
3013 0         0 my $ro = $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'};
3014 0         0 my $go = $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'};
3015 0         0 my $bo = $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'};
3016              
3017 0 0 0     0 if ($ro < $go && $go < $bo) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
3018 0         0 $self->{'COLOR_ORDER'} = RGB;
3019             } elsif ($bo < $go && $go < $ro) {
3020 0         0 $self->{'COLOR_ORDER'} = BGR;
3021             } elsif ($go < $ro && $ro < $bo) {
3022 0         0 $self->{'COLOR_ORDER'} = GRB;
3023             } elsif ($go < $bo && $bo < $ro) {
3024 0         0 $self->{'COLOR_ORDER'} = GBR;
3025             } elsif ($bo < $ro && $ro < $go) {
3026 0         0 $self->{'COLOR_ORDER'} = BRG;
3027             } elsif ($ro < $bo && $bo < $go) {
3028 0         0 $self->{'COLOR_ORDER'} = RBG;
3029             } else {
3030             # UNKNOWN - default to RGB
3031 0         0 $self->{'COLOR_ORDER'} = RGB;
3032             }
3033             }
3034              
3035             sub _screen_close {
3036 2     2   6 my $self = shift;
3037 2 50       11 unless (defined($self->{'ERROR'})) { # Only do it if not in emulation mode
3038 0 0       0 unmap($self->{'SCREEN'}) if (defined($self->{'SCREEN'})); # unmap had issues with File::Map.
3039 0 0       0 close($self->{'FB'}) if (defined($self->{'FB'}));
3040 0         0 delete($self->{'FB'}); # We leave no remnants
3041             }
3042 2         195 delete($self->{'SCREEN'});
3043             }
3044              
3045             sub text_mode {
3046             =head2 text_mode
3047              
3048             Sets the TTY into text mode, where text can interfere with the display
3049              
3050             =cut
3051              
3052 2     2 0 8 my $self = shift;
3053 2         90 c_text_mode($self->{'this_tty'});
3054             }
3055              
3056             sub graphics_mode {
3057             =head2 graphics_mode
3058              
3059             Sets the TTY in exclusive graphics mode, where text and cursor cannot interfere with the display. Please remember, you must call text_mode before exiting, else your console will not show any text!
3060              
3061             =cut
3062              
3063 0     0 0 0 my $self = shift;
3064 0         0 c_graphics_mode($self->{'this_tty'});
3065             }
3066              
3067             sub screen_dimensions {
3068             =head2 screen_dimensions
3069              
3070             When called in an array/list context:
3071              
3072             Returns the size and nature of the framebuffer in X,Y pixel values.
3073              
3074             It also returns the bits per pixel.
3075              
3076             =over 4
3077              
3078             my ($width,$height,$bits_per_pixel) = $fb->screen_dimensions();
3079              
3080             =back
3081              
3082             When called in a scalar context, it returns a hash reference:
3083              
3084             =over 4
3085              
3086             {
3087             'width' => pixel width of physical screen,
3088             'height' => pixel height of physical screen,
3089             'bits_per_pixel' => bits per pixel (16, 24, or 32),
3090             'bytes_per_line' => Number of bytes per scan line,
3091             'top_clip' => top edge of clipping rectangle (Y),
3092             'left_clip' => left edge of clipping rectangle (X),
3093             'bottom_clip' => bottom edge of clipping rectangle (YY),
3094             'right_clip' => right edge of clipping rectangle (XX),
3095             'width_clip' => width of clipping rectangle,
3096             'height_clip' => height of clipping rectangle,
3097             'color_order' => RGB, BGR, etc,
3098             }
3099              
3100             =back
3101              
3102             =cut
3103              
3104 1     1 0 1342 my $self = shift;
3105 1 50       5 if (wantarray) {
3106 0         0 return ($self->{'XRES'}, $self->{'YRES'}, $self->{'BITS'});
3107             } else {
3108             return (
3109             {
3110             'width' => $self->{'XRES'},
3111             'height' => $self->{'YRES'},
3112             'bits_per_pixel' => $self->{'BITS'},
3113             'bytes_per_line' => $self->{'BYTES_PER_LINE'},
3114             'top_clip' => $self->{'Y_CLIP'},
3115             'left_clip' => $self->{'X_CLIP'},
3116             'bottom_clip' => $self->{'YY_CLIP'},
3117             'right_clip' => $self->{'XX_CLIP'},
3118             'clip_width' => $self->{'W_CLIP'},
3119             'clip_height' => $self->{'H_CLIP'},
3120 1         32 'color_order' => $COLORORDER[$self->{'COLOR_ORDER'}],
3121             }
3122             );
3123             }
3124             }
3125              
3126             sub get_font_list {
3127             # Splash is now pulled in via "Graphics::Framebuffer::Splash"
3128              
3129             =head2 splash
3130              
3131             Displays the Splash screen. It automatically scales and positions to the clipping region.
3132              
3133             This is automatically displayed when this module is initialized, and the variable 'SPLASH' is true (which is the default).
3134              
3135             =over 4
3136              
3137             $fb->splash();
3138              
3139             =back
3140              
3141             =head2 get_font_list
3142              
3143             Returns an anonymous hash containing the font face names as keys and another anonymous hash assigned as the values for each key. This second hash contains the path to the font and the font's file name.
3144              
3145             =over 4
3146              
3147             'face name' => {
3148             'path' => 'path to font',
3149             'font' => 'file name of font'
3150             },
3151             ... The rest of the system fonts here
3152              
3153             =back
3154              
3155             You may also pass in a face name and it will return that face's information:
3156              
3157             =over 4
3158              
3159             my $font_info = $fb->get_font_list('DejaVuSerif');
3160              
3161             =back
3162              
3163             Would return something like:
3164              
3165             =over 4
3166              
3167             {
3168             'font' => 'dejavuserif.ttf',
3169             'path' => '/usr/share/fonts/truetype/'
3170             }
3171              
3172             =back
3173              
3174             When passing a name, it will return a hash reference (if only one match), or an array reference of hashes of fonts matching that name. Passing in "Arial" would return the font information for "Arial Black", "Arial Narrow", and "Arial Rounded" (if they are installed on your system).
3175              
3176             =cut
3177              
3178 0     0 1 0 my $self = shift;
3179 0         0 my ($filter) = @_;
3180              
3181 0         0 my $fonts;
3182 0 0       0 if ($filter) {
3183 0         0 foreach my $font (sort(keys %{ $self->{'FONTS'} })) {
  0         0  
3184 0 0       0 if ($font =~ /$filter/i) {
3185 0         0 push(@{$fonts}, $self->{'FONTS'}->{$font});
  0         0  
3186             }
3187             }
3188 0 0 0     0 if (defined($fonts) && scalar(@{$fonts}) == 1) {
  0         0  
3189 0         0 return ($fonts->[0]);
3190             } else {
3191 0         0 return ($fonts);
3192             }
3193             }
3194 0         0 return ($self->{'FONTS'});
3195             }
3196              
3197             sub draw_mode {
3198             =head2 draw_mode
3199              
3200             Sets or returns the drawing mode, depending on how it is called.
3201              
3202             =over 4
3203              
3204             my $draw_mode = $fb->draw_mode(); # Returns the current
3205             # Drawing mode.
3206              
3207             # Modes explained. These settings are global
3208              
3209             # When you draw it...
3210              
3211             $fb->draw_mode(NORMAL_MODE); # Replaces the screen pixel
3212             # with the new pixel. Imager
3213             # assisted drawing (acceleration)
3214             # only works in this mode.
3215              
3216             $fb->draw_mode(XOR_MODE); # Does a bitwise XOR with
3217             # the new pixel and screen
3218             # pixel.
3219              
3220             $fb->draw_mode(OR_MODE); # Does a bitwise OR with
3221             # the new pixel and screen
3222             # pixel.
3223              
3224             $fb->draw_mode(AND_MODE); # Does a bitwise AND with
3225             # the new pixel and screen
3226             # pixel.
3227              
3228             $fb->draw_mode(MASK_MODE); # If pixels in the source
3229             # are equal to the global
3230             # background color, then they
3231             # are not drawn (transparent).
3232              
3233             $fb->draw_mode(UNMASK_MODE); # Draws the new pixel on
3234             # screen areas only equal to
3235             # the background color.
3236              
3237             $fb->draw_mode(ALPHA_MODE); # Draws the new pixel on the screen
3238             # using the alpha channel value as
3239             # a transparency value. This means
3240             # the new pixel will not be
3241             # opague.
3242              
3243             $fb->draw_mode(ADD_MODE); # Draws the new pixel on the screen
3244             # by mathematically adding its pixel
3245             # value to the existing pixel value
3246              
3247             $fb->draw_mode(SUBTRACT_MODE); # Draws the new pixel on the screen
3248             # by mathematically subtracting the
3249             # the new pixel value from the existing
3250             # value
3251              
3252             $fb->draw_mode(MULTIPLY_MODE); # Draws the new pixel on the screen
3253             # by mathematically multiplying it with
3254             # the existing pixel value (usually not
3255             # too useful, but here for completeness)
3256              
3257             $fb->draw_mode(DIVIDE_MODE); # Draws the new pixel on the screen
3258             # by mathematically dividing it with the
3259             # existing pixel value (usually not too
3260             # useful, but here for completeness)
3261              
3262             =back
3263             =cut
3264              
3265 0     0 0 0 my $self = shift;
3266 0 0       0 if (@_) {
3267 0         0 my $mode = int(shift);
3268             # If not a valid value, then it defaults to normal mode
3269 0 0 0     0 $self->{'DRAW_MODE'} = ($mode <= 10 && $mode >= 0) ? $mode : NORMAL_MODE;
3270             } else {
3271 0         0 return ($self->{'DRAW_MODE'});
3272             }
3273             }
3274              
3275             sub normal_mode {
3276             =head2 normal_mode
3277              
3278             This is an alias to draw_mode(NORMAL_MODE)
3279              
3280             =over 4
3281              
3282             $fb->normal_mode();
3283              
3284             =back
3285              
3286             =cut
3287              
3288 0     0 0 0 my $self = shift;
3289 0         0 $self->draw_mode(NORMAL_MODE);
3290             }
3291              
3292             sub xor_mode {
3293             =head2 xor_mode
3294              
3295             This is an alias to draw_mode(XOR_MODE)
3296              
3297             =over 4
3298              
3299             $fb->xor_mode();
3300              
3301             =back
3302              
3303             =cut
3304              
3305 0     0 0 0 my $self = shift;
3306 0         0 $self->draw_mode(XOR_MODE);
3307             }
3308              
3309             sub or_mode {
3310             =head2 or_mode
3311              
3312             This is an alias to draw_mode(OR_MODE)
3313              
3314             =over 4
3315              
3316             $fb->or_mode();
3317              
3318             =back
3319              
3320             =cut
3321              
3322 0     0 0 0 my $self = shift;
3323 0         0 $self->draw_mode(OR_MODE);
3324             }
3325              
3326             sub alpha_mode {
3327             =head2 alpha_mode
3328              
3329             This is an alias to draw_mode(ALPHA_MODE)
3330              
3331             =over 4
3332              
3333             $fb->alpha_mode();
3334              
3335             =back
3336              
3337             =cut
3338              
3339 0     0 0 0 my $self = shift;
3340 0         0 $self->draw_mode(ALPHA_MODE);
3341             }
3342              
3343             sub and_mode {
3344             =head2 and_mode
3345              
3346             This is an alias to draw_mode(AND_MODE)
3347              
3348             =over 4
3349              
3350             $fb->and_mode();
3351              
3352             =back
3353              
3354             =cut
3355              
3356 0     0 0 0 my $self = shift;
3357 0         0 $self->draw_mode(AND_MODE);
3358             }
3359              
3360             sub mask_mode {
3361             =head2 mask_mode
3362              
3363             This is an alias to draw_mode(MASK_MODE)
3364              
3365             =over 4
3366              
3367             $fb->mask_mode();
3368              
3369             =back
3370              
3371             =cut
3372              
3373 0     0 0 0 my $self = shift;
3374 0         0 $self->draw_mode(MASK_MODE);
3375             }
3376              
3377             sub unmask_mode {
3378             =head2 unmask_mode
3379              
3380             This is an alias to draw_mode(UNMASK_MODE)
3381              
3382             =over 4
3383              
3384             $fb->unmask_mode();
3385              
3386             =back
3387              
3388             =cut
3389              
3390 0     0 0 0 my $self = shift;
3391 0         0 $self->draw_mode(UNMASK_MODE);
3392             }
3393              
3394             sub add_mode {
3395             =head2 add_mode
3396              
3397             This is an alias to draw_mode(ADD_MODE)
3398              
3399             =over 4
3400              
3401             $fb->add_mode();
3402              
3403             =back
3404              
3405             =cut
3406              
3407 0     0 0 0 my $self = shift;
3408 0         0 $self->draw_mode(ADD_MODE);
3409             }
3410              
3411             sub subtract_mode {
3412             =head2 subtract_mode
3413              
3414             This is an alias to draw_mode(SUBTRACT_MODE)
3415              
3416             =over 4
3417              
3418             $fb->subtract_mode();
3419              
3420             =back
3421              
3422             =cut
3423              
3424 0     0 0 0 my $self = shift;
3425 0         0 $self->draw_mode(SUBTRACT_MODE);
3426             }
3427              
3428             sub multiply_mode {
3429             =head2 multiply_mode
3430              
3431             This is an alias to draw_mode(MULTIPLY_MODE)
3432              
3433             =over 4
3434              
3435             $fb->multiply_mode();
3436              
3437             =back
3438              
3439             =cut
3440              
3441 0     0 0 0 my $self = shift;
3442 0         0 $self->draw_mode(MULTIPLY_MODE);
3443             }
3444              
3445             sub divide_mode {
3446             =head2 divide_mode
3447              
3448             This is an alias to draw_mode(DIVIDE_MODE)
3449              
3450             =over 4
3451              
3452             $fb->divide_mode();
3453              
3454             =back
3455              
3456             =cut
3457              
3458 0     0 0 0 my $self = shift;
3459 0         0 $self->draw_mode(DIVIDE_MODE);
3460             }
3461              
3462             sub clear_screen {
3463             =head2 clear_screen
3464              
3465             Fills the entire screen with the background color
3466              
3467             You can add an optional parameter to turn the console cursor on or off too.
3468              
3469             =over 4
3470              
3471             $fb->clear_screen(); # Leave cursor as is.
3472             $fb->clear_screen('OFF'); # Turn cursor OFF (Does nothing with emulated framebuffer mode).
3473             $fb->clear_screen('ON'); # Turn cursor ON (Does nothing with emulated framebuffer mode).
3474              
3475             =back
3476              
3477             =cut
3478              
3479             # Fills the entire screen with the background color fast #
3480 3     3 0 9 my $self = shift;
3481 3   100     42 my $cursor = shift || '';
3482              
3483 3 50       25 unless($self->{'DEVICE'} eq 'EMULATED') { # We only do this stuff to real framebuffers
3484 3 100       72 if ($cursor =~ /off/i) {
    100          
3485 1         8765 system('clear && tput civis -- invisible');
3486             } elsif ($cursor =~ /on/i) {
3487 1         7564 system('tput cnorm -- normal && clear');
3488             }
3489 3         71 select(STDOUT);
3490 3         45 $|++;
3491             }
3492 3 50       29 if ($self->{'CLIPPED'}) {
3493 0         0 my $w = $self->{'W_CLIP'};
3494 0         0 my $h = $self->{'H_CLIP'};
3495 0         0 $self->blit_write({ 'x' => $self->{'X_CLIP'}, 'y' => $self->{'Y_CLIP'}, 'width' => $w, 'height' => $h, 'image' => $self->{'RAW_BACKGROUND_COLOR'} x ($w * $h) }, 0);
3496             } else {
3497 3         6997 substr($self->{'SCREEN'}, 0) = $self->{'RAW_BACKGROUND_COLOR'} x ($self->{'fscreeninfo'}->{'smem_len'} / $self->{'BYTES'});
3498             }
3499 3         95 $self->_flush_screen();
3500             }
3501              
3502             sub cls {
3503             =head2 cls
3504              
3505             This is an alias to 'clear_screen'
3506              
3507             =cut
3508              
3509 3     3 0 1313 my $self = shift;
3510 3         29 $self->clear_screen(@_);
3511             }
3512              
3513             sub attribute_reset {
3514             =head2 attribute_reset
3515              
3516             Resets the plot point at 0,0. Resets clipping to the current screen size. Resets the global color to whatever 'FOREGROUND' is set to, and the global background color to whatever 'BACKGROUND' is set to, and resets the drawing mode to NORMAL.
3517              
3518             =over 4
3519              
3520             $fb->attribute_reset();
3521              
3522             =back
3523              
3524             =cut
3525              
3526 2     2 0 20 my $self = shift;
3527              
3528 2         12 $self->{'X'} = 0;
3529 2         16 $self->{'Y'} = 0;
3530 2         11 $self->set_color({ %{ $self->{'FOREGROUND'} } });
  2         177  
3531 2         170 $self->{'DRAW_MODE'} = NORMAL_MODE;
3532 2         6 $self->set_b_color({ %{ $self->{'BACKGROUND'} } });
  2         36  
3533 2         76 $self->clip_reset;
3534             }
3535              
3536             sub plot {
3537             =head2 plot
3538              
3539             Set a single pixel in the set foreground color at position x,y with the given pixel size (or default). Clipping applies.
3540              
3541             With 'pixel_size', if a positive number greater than 1, is drawn with square pixels. If it's a negative number, then it's drawn with round pixels. Square pixels are much faster.
3542              
3543             =over 4
3544              
3545             $fb->plot(
3546             {
3547             'x' => 20,
3548             'y' => 30,
3549             'pixel_size' => 3
3550             }
3551             );
3552              
3553             =back
3554              
3555             =cut
3556              
3557 0     0 0 0 my $self = shift;
3558 0         0 my $params = shift;
3559              
3560 0   0     0 my $x = int($params->{'x'} || 0); # Ignore decimals
3561 0   0     0 my $y = int($params->{'y'} || 0);
3562 0   0     0 my $size = int($params->{'pixel_size'} || 1);
3563 0         0 my ($c, $index);
3564 0 0       0 if (abs($size) > 1) {
    0          
3565 0 0       0 if ($size < -1) {
3566 0         0 $size = abs($size);
3567 0         0 $self->circle({ 'x' => $x, 'y' => $y, 'radius' => ($size / 2), 'filled' => 1, 'pixel_size' => 1 });
3568             } else {
3569 0         0 $self->rbox({ 'x' => $x - ($width / 2), 'y' => $y - ($height / 2), 'width' => $size, 'height' => $size, 'filled' => TRUE, 'pixel_size' => 1 });
3570             }
3571             } elsif ($self->{'ACCELERATED'}) {
3572             c_plot(
3573             $self->{'SCREEN'},
3574             $x, $y,
3575             $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'},
3576             $self->{'INT_RAW_FOREGROUND_COLOR'},
3577             $self->{'INT_RAW_BACKGROUND_COLOR'},
3578             $self->{'COLOR_ALPHA'},
3579             $self->{'DRAW_MODE'},
3580             $self->{'BYTES'},
3581             $self->{'BITS'},
3582             $self->{'BYTES_PER_LINE'},
3583 0         0 $self->{'XOFFSET'}, $self->{'YOFFSET'},
3584             );
3585             } else {
3586             # Only plot if the pixel is within the clipping region
3587 0 0 0     0 unless (($x > $self->{'XX_CLIP'}) || ($y > $self->{'YY_CLIP'}) || ($x < $self->{'X_CLIP'}) || ($y < $self->{'Y_CLIP'})) {
      0        
      0        
3588             # The 'history' is a 'draw_arc' optimization and beautifier for xor mode. It only draws pixels not in
3589             # the history buffer.
3590 0 0 0     0 unless (exists($self->{'history'}) && defined($self->{'history'}->{$y}->{$x})) {
3591 0         0 $index = ($self->{'BYTES_PER_LINE'} * ($y + $self->{'YOFFSET'})) + (($self->{'XOFFSET'} + $x) * $self->{'BYTES'});
3592 0 0 0     0 if ($index >= 0 && $index <= ($self->{'fscreeninfo'}->{'smem_len'} - $self->{'BYTES'})) {
3593 0         0 eval {
3594 0   0     0 $c = substr($self->{'SCREEN'}, $index, $self->{'BYTES'}) || chr(0) x $self->{'BYTES'};
3595 0 0       0 if ($self->{'DRAW_MODE'} == NORMAL_MODE) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
3596 0         0 $c = $self->{'RAW_FOREGROUND_COLOR'};
3597             } elsif ($self->{'DRAW_MODE'} == XOR_MODE) {
3598 0         0 $c ^= $self->{'RAW_FOREGROUND_COLOR'};
3599             } elsif ($self->{'DRAW_MODE'} == OR_MODE) {
3600 0         0 $c |= $self->{'RAW_FOREGROUND_COLOR'};
3601             } elsif ($self->{'DRAW_MODE'} == ALPHA_MODE) {
3602 0         0 my $back = $self->get_pixel({ 'x' => $x, 'y' => $y });
3603 0         0 my $saved = { 'main' => $self->{'RAW_FOREGROUND_COLOR'} };
3604 0         0 foreach my $color (qw( red green blue )) {
3605 0         0 $saved->{$color} = $self->{ 'COLOR_' . uc($color) };
3606 0         0 $back->{$color} = ($self->{ 'COLOR_' . uc($color) } * $self->{'COLOR_ALPHA'}) + ($back->{$color} * (1 - $self->{'COLOR_ALPHA'}));
3607             }
3608 0         0 $back->{'alpha'} = min(255, $self->{'COLOR_ALPHA'} + $back->{'alpha'});
3609 0         0 $self->set_color($back);
3610 0         0 $c = $self->{'RAW_FOREGROUND_COLOR'};
3611 0         0 $self->{'RAW_FOREGROUND_COLOR'} = $saved->{'main'};
3612 0         0 foreach my $color (qw( red green blue )) {
3613 0         0 $self->{ 'COLOR_' . uc($color) } = $saved->{$color};
3614             }
3615             } elsif ($self->{'DRAW_MODE'} == AND_MODE) {
3616 0         0 $c &= $self->{'RAW_FOREGROUND_COLOR'};
3617             } elsif ($self->{'DRAW_MODE'} == ADD_MODE) {
3618 0         0 $c += $self->{'RAW_FOREGROUND_COLOR'};
3619             } elsif ($self->{'DRAW_MODE'} == SUBTRACT_MODE) {
3620 0         0 $c -= $self->{'RAW_FOREGROUND_COLOR'};
3621             } elsif ($self->{'DRAW_MODE'} == MULTIPLY_MODE) {
3622 0         0 $c *= $self->{'RAW_FOREGROUND_COLOR'};
3623             } elsif ($self->{'DRAW_MODE'} == DIVIDE_MODE) {
3624 0         0 $c /= $self->{'RAW_FOREGROUND_COLOR'};
3625             } elsif ($self->{'DRAW_MODE'} == MASK_MODE) {
3626 0 0       0 if ($self->{'BITS'} == 32) {
3627 0 0       0 $c = $self->{'RAW_FOREGROUND_COLOR'} if (substr($self->{'RAW_FOREGROUND_COLOR'}, 0, 3) ne substr($self->{'RAW_BACKGROUND_COLOR'}, 0, 3));
3628             } else {
3629 0 0       0 $c = $self->{'RAW_FOREGROUND_COLOR'} if ($self->{'RAW_FOREGROUND_COLOR'} ne $self->{'RAW_BACKGROUND_COLOR'});
3630             }
3631             } elsif ($self->{'DRAW_MODE'} == UNMASK_MODE) {
3632 0         0 my $pixel = $self->pixel({ 'x' => $x, 'y' => $y });
3633 0         0 my $raw = $pixel->{'raw'};
3634 0 0       0 if ($self->{'BITS'} == 32) {
3635 0 0       0 $c = $self->{'RAW_FOREGROUND_COLOR'} if (substr($raw, 0, 3) eq substr($self->{'RAW_BACKGROUND_COLOR'}, 0, 3));
3636             } else {
3637 0 0       0 $c = $self->{'RAW_FOREGROUND_COLOR'} if ($raw eq $self->{'RAW_BACKGROUND_COLOR'});
3638             }
3639             }
3640 0         0 substr($self->{'SCREEN'}, $index, $self->{'BYTES'}) = $c;
3641             };
3642 0         0 my $error = $@;
3643 0 0 0     0 warn __LINE__ . " $error" if ($error && $self->{'SHOW_ERRORS'});
3644 0 0       0 $self->_fix_mapping() if ($error);
3645             }
3646 0 0       0 $self->{'history'}->{$y}->{$x} = 1 if (exists($self->{'history'}));
3647             }
3648             }
3649             }
3650              
3651 0         0 $self->{'X'} = $x;
3652 0         0 $self->{'Y'} = $y;
3653             }
3654              
3655             sub setpixel {
3656             =head2 setpixel
3657              
3658             Same as 'plot' above
3659              
3660             =cut
3661              
3662 0     0 0 0 my $self = shift;
3663 0         0 $self->plot(shift);
3664             }
3665              
3666             sub pixel {
3667             =head2 pixel
3668              
3669             Returns the color of the pixel at coordinate x,y, if it lies within the clipping region. It returns undefined if outside of the clipping region.
3670              
3671             =over 4
3672              
3673             my $pixel = $fb->pixel({'x' => 20,'y' => 25});
3674              
3675             $pixel is a hash reference in the form:
3676              
3677             {
3678             'red' => integer value, # 0 - 255
3679             'green' => integer value, # 0 - 255
3680             'blue' => integer value, # 0 - 255
3681             'alpha' => integer value, # 0 - 255
3682             'hex' => hexadecimal string of the values from 00000000 to FFFFFFFF
3683             'raw' => 16/24/32bit encoded string (depending on screen mode)
3684             }
3685              
3686             =back
3687             =cut
3688              
3689 0     0 0 0 my $self = shift;
3690 0         0 my $params = shift;
3691              
3692 0         0 my $x = int($params->{'x'});
3693 0         0 my $y = int($params->{'y'});
3694 0         0 my $bytes = $self->{'BYTES'};
3695              
3696             # Values outside of the clipping area return undefined.
3697 0 0 0     0 unless (($x > $self->{'XX_CLIP'}) || ($y > $self->{'YY_CLIP'}) || ($x < $self->{'X_CLIP'}) || ($y < $self->{'Y_CLIP'})) {
      0        
      0        
3698 0         0 my ($R, $G, $B);
3699 0         0 my $index = ($self->{'BYTES_PER_LINE'} * ($y + $self->{'YOFFSET'})) + (($self->{'XOFFSET'} + $x) * $bytes);
3700 0         0 my $color = substr($self->{'SCREEN'}, $index, $bytes);
3701              
3702 0 0       0 return($color) if (exists($params->{'raw'})); # Bypass the mess below if floodfill is using this
3703              
3704 0         0 my $color_order = $self->{'COLOR_ORDER'};
3705 0         0 my $A = $self->{'COLOR_ALPHA'};
3706 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
3707 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
3708 0         0 ($B, $G, $R, $A) = unpack("C$bytes", $color);
3709             } elsif ($color_order == BRG) {
3710 0         0 ($B, $R, $G, $A) = unpack("C$bytes", $color);
3711             } elsif ($color_order == RGB) {
3712 0         0 ($R, $G, $B, $A) = unpack("C$bytes", $color);
3713             } elsif ($color_order == RBG) {
3714 0         0 ($R, $B, $G, $A) = unpack("C$bytes", $color);
3715             } elsif ($color_order == GRB) {
3716 0         0 ($G, $R, $B, $A) = unpack("C$bytes", $color);
3717             } elsif ($color_order == GBR) {
3718 0         0 ($G, $B, $R, $A) = unpack("C$bytes", $color);
3719             }
3720             } elsif ($self->{'BITS'} == 24) {
3721 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
3722 0         0 ($B, $G, $R) = unpack("C$bytes", $color);
3723             } elsif ($color_order == BRG) {
3724 0         0 ($B, $R, $G) = unpack("C$bytes", $color);
3725             } elsif ($color_order == RGB) {
3726 0         0 ($R, $G, $B) = unpack("C$bytes", $color);
3727             } elsif ($color_order == RBG) {
3728 0         0 ($R, $B, $G) = unpack("C$bytes", $color);
3729             } elsif ($color_order == GRB) {
3730 0         0 ($G, $R, $B) = unpack("C$bytes", $color);
3731             } elsif ($color_order == GBR) {
3732 0         0 ($G, $B, $R) = unpack("C$bytes", $color);
3733             }
3734             } elsif ($self->{'BITS'} == 16) {
3735 0         0 my $C = unpack('S', $color);
3736              
3737 0 0       0 $B = ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'} < 6) ? ($C >> ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'})) & 31 : ($C >> ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'})) & 63;
3738 0 0       0 $G = ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'} < 6) ? ($C >> ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) & 31 : ($C >> ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) & 63;
3739 0 0       0 $R = ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'} < 6) ? ($C >> ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) & 31 : ($C >> ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) & 63;
3740 0         0 $R = $R << (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'});
3741 0         0 $G = $G << (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'});
3742 0         0 $B = $B << (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});
3743             }
3744 0         0 return ({ 'red' => $R, 'green' => $G, 'blue' => $B, 'alpha' => $A, 'raw' => $color, 'hex' => sprintf('%02x%02x%02x%02x',$R,$G,$B,$A) });
3745             }
3746 0         0 return (undef);
3747             }
3748              
3749             sub get_pixel {
3750             =head2 get_pixel
3751              
3752             Alias for 'pixel'.
3753              
3754             =cut
3755              
3756 0     0 0 0 my $self = shift;
3757 0         0 return ($self->pixel(shift));
3758             }
3759              
3760             sub last_plot {
3761             =head2 last_plot
3762              
3763             Returns the last plotted position
3764              
3765             =over 4
3766              
3767             my $last_plot = $fb->last_plot();
3768              
3769             This returns an anonymous hash reference in the form:
3770              
3771             {
3772             'x' => x position,
3773             'y' => y position
3774             }
3775              
3776             =back
3777              
3778             Or, if you want a simple array returned:
3779              
3780             =over 4
3781              
3782             my ($x,$y) = $fb->last_plot();
3783              
3784             This returns the position as a two element array:
3785              
3786             ( x position, y position )
3787              
3788             =back
3789              
3790             =cut
3791              
3792 0     0 0 0 my $self = shift;
3793 0 0       0 if (wantarray) {
3794 0         0 return ($self->{'X'}, $self->{'Y'});
3795             }
3796 0         0 return ({ 'x' => $self->{'X'}, 'y' => $self->{'Y'} });
3797             }
3798              
3799             sub line {
3800             =head2 line
3801              
3802             Draws a line, in the foreground color, from point x,y to point xx,yy. Clipping applies.
3803              
3804             =over 4
3805              
3806             $fb->line({
3807             'x' => 50,
3808             'y' => 60,
3809             'xx' => 100,
3810             'yy' => 332
3811             'pixel_size' => 1,
3812             'antialiased' => TRUE
3813             });
3814              
3815             =back
3816              
3817             =cut
3818              
3819 0     0 0 0 my $self = shift;
3820 0         0 my $params = shift;
3821              
3822 0         0 $self->plot($params);
3823 0         0 $params->{'x'} = $params->{'xx'};
3824 0         0 $params->{'y'} = $params->{'yy'};
3825 0         0 $self->drawto($params);
3826             }
3827              
3828             sub angle_line {
3829             =head2 angle_line
3830              
3831             Draws a line, in the global foreground color, from point x,y at an angle of 'angle', of length 'radius'. Clipping applies.
3832              
3833             =over 4
3834              
3835             $fb->angle_line({
3836             'x' => 50,
3837             'y' => 60,
3838             'radius' => 50,
3839             'angle' => 30.3, # Compass coordinates (0-360)
3840             'pixel_size' => 3,
3841             'antialiased' => FALSE
3842             });
3843              
3844             =back
3845              
3846             * This is not affected by the Acceleration setting
3847              
3848             =cut
3849              
3850 0     0 0 0 my $self = shift;
3851 0         0 my $params = shift;
3852              
3853 0         0 my ($dp_cos, $dp_sin);
3854 0         0 my $index = int($params->{'angle'} * 100);
3855              
3856 0 0       0 if (defined($self->{'dp_cache'}->[$index])) {
3857 0         0 ($dp_cos, $dp_sin) = (@{ $self->{'dp_cache'}->[$index] });
  0         0  
3858             } else {
3859 0         0 my $dp = ($params->{'angle'} * pi) / 180;
3860 0         0 ($dp_cos, $dp_sin) = (cos($dp), sin($dp));
3861 0         0 $self->{'dp_cache'}->[$index] = [$dp_cos, $dp_sin];
3862             }
3863 0         0 $params->{'xx'} = int($params->{'x'} - ($params->{'radius'} * $dp_sin));
3864 0         0 $params->{'yy'} = int($params->{'y'} - ($params->{'radius'} * $dp_cos));
3865 0         0 $self->line($params);
3866             }
3867              
3868             sub drawto {
3869             =head2 drawto
3870              
3871             Draws a line, in the foreground color, from the last plotted position to the position x,y. Clipping applies.
3872              
3873             =over 4
3874              
3875             $fb->drawto({
3876             'x' => 50,
3877             'y' => 60,
3878             'pixel_size' => 2,
3879             'antialiased' => TRUE
3880             });
3881              
3882             =back
3883              
3884             * Antialiased lines are not accelerated
3885              
3886             =cut
3887              
3888             ##########################################################################
3889             # For Perl, Perfectly horizontal line drawing is optimized by using the #
3890             # BLIT functions. This assists greatly with drawing filled objects. In #
3891             # fact, it's hundreds of times faster! #
3892             ##########################################################################
3893 0     0 0 0 my $self = shift;
3894 0         0 my $params = shift;
3895              
3896 0         0 my $x_end = int($params->{'x'});
3897 0         0 my $y_end = int($params->{'y'});
3898 0   0     0 my $size = int($params->{'pixel_size'} || 1);
3899              
3900 0         0 my $start_x = $self->{'X'};
3901 0         0 my $start_y = $self->{'Y'};
3902 0   0     0 my $antialiased = $params->{'antialiased'} || 0;
3903 0         0 my $XX = $x_end;
3904 0         0 my $YY = $y_end;
3905              
3906 0 0 0     0 if ($self->{'ACCELERATED'} && $size == 1 && !$antialiased) {
      0        
3907             c_line(
3908             $self->{'SCREEN'},
3909             $start_x, $start_y, $x_end, $y_end,
3910             $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'},
3911             $self->{'INT_RAW_FOREGROUND_COLOR'},
3912             $self->{'INT_RAW_BACKGROUND_COLOR'},
3913             $self->{'COLOR_ALPHA'},
3914             $self->{'DRAW_MODE'},
3915             $self->{'BYTES'},
3916             $self->{'BITS'},
3917             $self->{'BYTES_PER_LINE'},
3918 0         0 $self->{'XOFFSET'}, $self->{'YOFFSET'},
3919             # $antialiased,
3920             );
3921             } else {
3922 0         0 my ($width, $height);
3923             # Determines if the coordinates sent were right-side-up or up-side-down.
3924 0 0       0 if ($start_x > $x_end) {
3925 0         0 $width = $start_x - $x_end;
3926             } else {
3927 0         0 $width = $x_end - $start_x;
3928             }
3929 0 0       0 if ($start_y > $y_end) {
3930 0         0 $height = $start_y - $y_end;
3931             } else {
3932 0         0 $height = $y_end - $start_y;
3933             }
3934              
3935             # We need only plot if start and end are the same
3936 0 0 0     0 if (($x_end == $start_x) && ($y_end == $start_y)) {
    0          
    0          
    0          
    0          
    0          
3937 0         0 $self->plot({ 'x' => $x_end, 'y' => $y_end, 'pixel_size' => $size });
3938              
3939             # Else, let's get to drawing
3940             } elsif ($x_end == $start_x) { # Draw a perfectly verticle line
3941 0 0       0 if ($start_y > $y_end) { # Draw direction is UP
3942 0         0 foreach my $y ($y_end .. $start_y) {
3943 0         0 $self->plot({ 'x' => $start_x, 'y' => $y, 'pixel_size' => $size });
3944             }
3945             } else { # Draw direction is DOWN
3946 0         0 foreach my $y ($start_y .. $y_end) {
3947 0         0 $self->plot({ 'x' => $start_x, 'y' => $y, 'pixel_size' => $size });
3948             }
3949             }
3950             } elsif ($y_end == $start_y) { # Draw a perfectly horizontal line (fast)
3951 0         0 $x_end = max($self->{'X_CLIP'}, min($x_end, $self->{'XX_CLIP'}));
3952 0         0 $start_x = max($self->{'X_CLIP'}, min($start_x, $self->{'XX_CLIP'}));
3953 0         0 $width = abs($x_end - $start_x);
3954 0 0       0 if ($size == 1) {
3955 0 0       0 if ($start_x > $x_end) {
3956 0         0 $self->blit_write({ 'x' => $x_end, 'y' => $y_end, 'width' => $width, 'height' => 1, 'image' => $self->{'RAW_FOREGROUND_COLOR'} x $width }); # Blitting a horizontal line is much faster!
3957             } else {
3958 0         0 $self->blit_write({ 'x' => $start_x, 'y' => $start_y, 'width' => $width, 'height' => 1, 'image' => $self->{'RAW_FOREGROUND_COLOR'} x $width }); # Blitting a horizontal line is much faster!
3959             }
3960             } else {
3961 0 0       0 if ($start_x > $x_end) {
3962 0         0 $self->blit_write({ 'x' => $x_end, 'y' => ($y_end - ($size / 2)), 'width' => $width, 'height' => $size, 'image' => $self->{'RAW_FOREGROUND_COLOR'} x ($width * $size) }); # Blitting a horizontal line is much faster!
3963             } else {
3964 0         0 $self->blit_write({ 'x' => $start_x, 'y' => ($y_end - ($size / 2)), 'width' => $width, 'height' => $size, 'image' => $self->{'RAW_FOREGROUND_COLOR'} x ($width * $size) }); # Blitting a horizontal line is much faster!
3965             }
3966             }
3967             } elsif ($antialiased) {
3968 0         0 $self->_draw_line_antialiased($start_x, $start_y, $x_end, $y_end);
3969             } elsif ($width > $height) { # Wider than it is high
3970 0         0 my $factor = $height / $width;
3971 0 0 0     0 if (($start_x < $x_end) && ($start_y < $y_end)) { # Draw UP and to the RIGHT
    0 0        
    0 0        
    0 0        
3972 0         0 while ($start_x < $x_end) {
3973 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
3974 0         0 $start_y += $factor;
3975 0         0 $start_x++;
3976             }
3977             } elsif (($start_x > $x_end) && ($start_y < $y_end)) { # Draw UP and to the LEFT
3978 0         0 while ($start_x > $x_end) {
3979 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
3980 0         0 $start_y += $factor;
3981 0         0 $start_x--;
3982             }
3983             } elsif (($start_x < $x_end) && ($start_y > $y_end)) { # Draw DOWN and to the RIGHT
3984 0         0 while ($start_x < $x_end) {
3985 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
3986 0         0 $start_y -= $factor;
3987 0         0 $start_x++;
3988             }
3989             } elsif (($start_x > $x_end) && ($start_y > $y_end)) { # Draw DOWN and to the LEFT
3990 0         0 while ($start_x > $x_end) {
3991 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
3992 0         0 $start_y -= $factor;
3993 0         0 $start_x--;
3994             }
3995             }
3996             } elsif ($width < $height) { # Higher than it is wide
3997 0         0 my $factor = $width / $height;
3998 0 0 0     0 if (($start_x < $x_end) && ($start_y < $y_end)) { # Draw UP and to the RIGHT
    0 0        
    0 0        
    0 0        
3999 0         0 while ($start_y < $y_end) {
4000 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4001 0         0 $start_x += $factor;
4002 0         0 $start_y++;
4003             }
4004             } elsif (($start_x > $x_end) && ($start_y < $y_end)) { # Draw UP and to the LEFT
4005 0         0 while ($start_y < $y_end) {
4006 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4007 0         0 $start_x -= $factor;
4008 0         0 $start_y++;
4009             }
4010             } elsif (($start_x < $x_end) && ($start_y > $y_end)) { # Draw DOWN and to the RIGHT
4011 0         0 while ($start_y > $y_end) {
4012 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4013 0         0 $start_x += $factor;
4014 0         0 $start_y--;
4015             }
4016             } elsif (($start_x > $x_end) && ($start_y > $y_end)) { # Draw DOWN and to the LEFT
4017 0         0 while ($start_y > $y_end) {
4018 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4019 0         0 $start_x -= $factor;
4020 0         0 $start_y--;
4021             }
4022             }
4023             } else { # $width == $height
4024 0 0 0     0 if (($start_x < $x_end) && ($start_y < $y_end)) { # Draw UP and to the RIGHT
    0 0        
    0 0        
    0 0        
4025 0         0 while ($start_y < $y_end) {
4026 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4027 0         0 $start_x++;
4028 0         0 $start_y++;
4029             }
4030             } elsif (($start_x > $x_end) && ($start_y < $y_end)) { # Draw UP and to the LEFT
4031 0         0 while ($start_y < $y_end) {
4032 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4033 0         0 $start_x--;
4034 0         0 $start_y++;
4035             }
4036             } elsif (($start_x < $x_end) && ($start_y > $y_end)) { # Draw DOWN and to the RIGHT
4037 0         0 while ($start_y > $y_end) {
4038 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4039 0         0 $start_x++;
4040 0         0 $start_y--;
4041             }
4042             } elsif (($start_x > $x_end) && ($start_y > $y_end)) { # Draw DOWN and to the LEFT
4043 0         0 while ($start_y > $y_end) {
4044 0         0 $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
4045 0         0 $start_x--;
4046 0         0 $start_y--;
4047             }
4048             }
4049              
4050             }
4051             }
4052 0         0 $self->{'X'} = $XX;
4053 0         0 $self->{'Y'} = $YY;
4054             }
4055              
4056             sub _flush_screen {
4057             # Since the framebuffer is mappeed as a string device, Perl buffers the output, and this must be flushed.
4058 5     5   24 my $self = shift;
4059 5 50       35 unless ($self->{'DEVICE'} eq 'EMULATED') {
4060 5         68 select(STDERR);
4061 5         32 $|++;
4062             }
4063 5         45 select($self->{'FB'});
4064 5         177 $|++;
4065             }
4066              
4067             sub _adj_plot {
4068             # Part of antialiased drawing
4069 0     0   0 my $self = shift;
4070 0         0 my $x = shift;
4071 0         0 my $y = shift;
4072 0         0 my $c = shift;
4073 0         0 my $s = shift;
4074              
4075 0         0 $self->set_color({ 'red' => $s->{'red'} * $c, 'green' => $s->{'green'} * $c, 'blue' => $s->{'blue'} * $c });
4076 0         0 $self->plot({ 'x' => $x, 'y' => $y });
4077             }
4078              
4079             sub _draw_line_antialiased {
4080 0     0   0 my $self = shift;
4081 0         0 my $x0 = shift;
4082 0         0 my $y0 = shift;
4083 0         0 my $x1 = shift;
4084 0         0 my $y1 = shift;
4085              
4086 0         0 my $saved = { %{ $self->{'SET_RAW_FOREGROUND_COLOR'} } };
  0         0  
4087              
4088 0         0 my $plot = \&_adj_plot;
4089              
4090 0 0       0 if (abs($y1 - $y0) > abs($x1 - $x0)) {
4091 0     0   0 $plot = sub { _adj_plot(@_[0, 2, 1, 3, 4]) };
  0         0  
4092 0         0 ($x0, $y0, $x1, $y1) = ($y0, $x0, $y1, $x1);
4093             }
4094              
4095 0 0       0 if ($x0 > $x1) {
4096 0         0 ($x0, $x1, $y0, $y1) = ($x1, $x0, $y1, $y0);
4097             }
4098              
4099 0         0 my $dx = $x1 - $x0;
4100 0         0 my $dy = $y1 - $y0;
4101 0         0 my $gradient = $dy / $dx;
4102              
4103 0         0 my @xends;
4104             my $intery;
4105              
4106             # handle the endpoints
4107 0         0 foreach my $xy ([$x0, $y0], [$x1, $y1]) {
4108 0         0 my ($x, $y) = @{$xy};
  0         0  
4109 0         0 my $xend = int($x + 0.5); # POSIX::lround($x);
4110 0         0 my $yend = $y + $gradient * ($xend - $x);
4111 0         0 my $xgap = _rfpart($x + 0.5);
4112              
4113 0         0 my $x_pixel = $xend;
4114 0         0 my $y_pixel = int($yend);
4115 0         0 push(@xends, $x_pixel);
4116              
4117 0         0 $plot->($self, $x_pixel, $y_pixel, _rfpart($yend) * $xgap, $saved);
4118 0         0 $plot->($self, $x_pixel, $y_pixel + 1, _fpart($yend) * $xgap, $saved);
4119 0 0       0 next if (defined($intery));
4120              
4121             # first y-intersection for the main loop
4122 0         0 $intery = $yend + $gradient;
4123             }
4124              
4125             # main loop
4126              
4127 0         0 foreach my $x ($xends[0] + 1 .. $xends[1] - 1) {
4128 0         0 $plot->($self, $x, int($intery), _rfpart($intery), $saved);
4129 0         0 $plot->($self, $x, int($intery) + 1, _fpart($intery), $saved);
4130 0         0 $intery += $gradient;
4131             }
4132 0         0 $self->set_color($saved);
4133             }
4134              
4135             sub bezier {
4136             =head2 bezier
4137              
4138             Draws a Bezier curve, based on a list of control points.
4139              
4140             =over 4
4141              
4142             $fb->bezier(
4143             {
4144             'coordinates' => [
4145             x0,y0,
4146             x1,y1,
4147             ... # As many as needed
4148             ],
4149             'points' => 100, # Number of total points plotted for curve
4150             # The higher the number, the smoother the curve.
4151             'pixel_size' => 2, # optional
4152             'closed' => 1, # optional, close it and make it a full shape.
4153             'filled' => 1 # Results may vary, optional
4154             'gradient' => {
4155             'direction' => 'horizontal', # or vertical
4156             'colors' => { # 2 to any number of transitions allowed
4157             'red' => [255,255,0], # Red to yellow to cyan
4158             'green' => [0,255,255],
4159             'blue' => [0,0,255]
4160             }
4161             }
4162             }
4163             );
4164              
4165             =back
4166              
4167             * This is not affected by the Acceleration setting
4168              
4169             =cut
4170              
4171 0     0 0 0 my $self = shift;
4172 0         0 my $params = shift;
4173              
4174 0   0     0 my $size = $params->{'pixel_size'} || 1;
4175 0   0     0 my $closed = $params->{'closed'} || 0;
4176 0   0     0 my $filled = $params->{'filled'} || 0;
4177              
4178 0 0       0 push(@{ $params->{'coordinates'} }, $params->{'coordinates'}->[0], $params->{'coordinates'}->[1]) if ($closed);
  0         0  
4179              
4180 0         0 my $bezier = Math::Bezier->new($params->{'coordinates'});
4181 0   0     0 my @coords = $bezier->curve($params->{'points'} || (scalar(@{ $params->{'coordinates'} }) / 2));
4182 0 0       0 if ($closed) {
4183 0         0 $params->{'coordinates'} = \@coords;
4184 0         0 $self->polygon($params);
4185             } else {
4186 0         0 $self->plot({ 'x' => shift(@coords), 'y' => shift(@coords), 'pixel_size' => $size });
4187 0         0 while (scalar(@coords)) {
4188 0         0 $self->drawto({ 'x' => shift(@coords), 'y' => shift(@coords), 'pixel_size' => $size });
4189             }
4190             }
4191             }
4192              
4193             sub cubic_bezier {
4194             =head2 cubic_bezier
4195              
4196             DISCONTINUED, use 'bezier' instead (now just an alias to 'bezier')
4197              
4198             =cut
4199              
4200 0     0 0 0 my $self = shift;
4201 0         0 $self->bezier(shift);
4202             }
4203              
4204             sub draw_arc {
4205             =head2 draw_arc
4206              
4207             Draws an arc/pie/poly arc of a circle at point x,y.
4208              
4209             =over 4
4210              
4211             x = x of center of circle
4212             y = y of center of circle
4213             radius = radius of circle
4214              
4215             start_degrees = starting point, in degrees, of arc
4216              
4217             end_degrees = ending point, in degrees, of arc
4218              
4219             granularity = This is used for accuracy in drawing
4220             the arc. The smaller the number, the
4221             more accurate the arc is drawn, but it
4222             is also slower. Values between 0.1
4223             and 0.01 are usually good. Valid values
4224             are any positive floating point number
4225             down to 0.0001. Anything smaller than
4226             that is just silly.
4227              
4228             mode = Specifies the drawing mode.
4229             0 > arc only
4230             1 > Filled pie section
4231             Can have gradients, textures, and hatches
4232             2 > Poly arc. Draws a line from x,y to the
4233             beginning and ending arc position.
4234              
4235             $fb->draw_arc({
4236             'x' => 100,
4237             'y' => 100,
4238             'radius' => 100,
4239             'start_degrees' => -40, # Compass coordinates
4240             'end_degrees' => 80,
4241             'granularity => .05,
4242             'mode' => 2 # The object hash has 'ARC', 'PIE',
4243             # and 'POLY_ARC' as a means of filling
4244             # this value.
4245             });
4246              
4247             =back
4248              
4249             * Only PIE is affected by the acceleration setting.
4250              
4251             =cut
4252              
4253             # This isn't exactly the fastest routine out there, hence the "granularity" parameter, but it is pretty neat. Drawing lines between points smooths and compensates for high granularity settings.
4254 0     0 0 0 my $self = shift;
4255 0         0 my $params = shift;
4256              
4257 0         0 my $x = int($params->{'x'});
4258 0         0 my $y = int($params->{'y'});
4259 0   0     0 my $radius = int($params->{'radius'} || 1);
4260 0         0 $radius = max($radius, 1);
4261 0   0     0 my $start_degrees = $params->{'start_degrees'} || 0;
4262 0   0     0 my $end_degrees = $params->{'end_degrees'} || 360;
4263 0   0     0 my $granularity = $params->{'granularity'} || .1;
4264              
4265 0   0     0 my $mode = int($params->{'mode'} || 0);
4266 0   0     0 my $size = int($params->{'pixel_size'} || 1);
4267 0         0 my $bytes = $self->{'BYTES'};
4268              
4269 0         0 $start_degrees -= 90;
4270 0         0 $end_degrees -= 90;
4271 0 0       0 $start_degrees += 360 if ($start_degrees < 0);
4272 0 0       0 $end_degrees += 360 if ($end_degrees < 0);
4273              
4274 0 0 0     0 unless ($self->{'ACCELERATED'} && $mode == PIE) { # ($mode == PIE || $mode == ARC)) {
4275 0         0 my ($sx, $sy, $degrees, $ox, $oy) = (0, 0, 1, 1, 1);
4276 0         0 my @coords;
4277              
4278 0         0 my $plotted = FALSE;
4279 0         0 $degrees = $start_degrees;
4280 0         0 my ($dp_cos, $dp_sin);
4281 0 0       0 if ($start_degrees > $end_degrees) {
4282 0         0 do {
4283 0         0 my $index = int($degrees * 100);
4284 0 0       0 if (defined($self->{'dp_cache'}->[$index])) {
4285 0         0 ($dp_cos, $dp_sin) = (@{ $self->{'dp_cache'}->[$index] });
  0         0  
4286             } else {
4287 0         0 my $dp = ($degrees * pi) / 180;
4288 0         0 ($dp_cos, $dp_sin) = (cos($dp), sin($dp));
4289 0         0 $self->{'dp_cache'}->[$index] = [$dp_cos, $dp_sin];
4290             }
4291 0         0 $sx = int($x - ($radius * $dp_sin));
4292 0         0 $sy = int($y - ($radius * $dp_cos));
4293 0 0 0     0 if (($sx <=> $ox) || ($sy <=> $oy)) {
4294 0 0       0 if ($mode == ARC) { # Ordinary arc
4295 0 0       0 if ($plotted) { # Fills in the gaps better this way
4296 0         0 $self->drawto({ 'x' => $sx, 'y' => $sy, 'pixel_size' => $size });
4297             } else {
4298 0         0 $self->plot({ 'x' => $sx, 'y' => $sy, 'pixel_size' => $size });
4299 0         0 $plotted = TRUE;
4300             }
4301             } else {
4302 0 0       0 if ($degrees == $start_degrees) {
4303 0         0 push(@coords, $x, $y, $sx, $sy);
4304             } else {
4305 0         0 push(@coords, $sx, $sy);
4306             }
4307             }
4308 0         0 $ox = $sx;
4309 0         0 $oy = $sy;
4310             }
4311 0         0 $degrees += $granularity;
4312             } until ($degrees >= 360);
4313 0         0 $degrees = 0;
4314             }
4315 0         0 $plotted = FALSE;
4316 0         0 do {
4317 0         0 my $index = int($degrees * 100);
4318 0 0       0 if (defined($self->{'dp_cache'}->[$index])) {
4319 0         0 ($dp_cos, $dp_sin) = (@{ $self->{'dp_cache'}->[$index] });
  0         0  
4320             } else {
4321 0         0 my $dp = ($degrees * pi) / 180;
4322 0         0 ($dp_cos, $dp_sin) = (cos($dp), sin($dp));
4323 0         0 $self->{'dp_cache'}->[$index] = [$dp_cos, $dp_sin];
4324             }
4325 0         0 $sx = int($x - ($radius * $dp_sin));
4326 0         0 $sy = int($y - ($radius * $dp_cos));
4327 0 0 0     0 if (($sx <=> $ox) || ($sy <=> $oy)) {
4328 0 0       0 if ($mode == ARC) { # Ordinary arc
4329 0 0       0 if ($plotted) { # Fills in the gaps better this way
4330 0         0 $self->drawto({ 'x' => $sx, 'y' => $sy, 'pixel_size' => $size });
4331             } else {
4332 0         0 $self->plot({ 'x' => $sx, 'y' => $sy, 'pixel_size' => $size });
4333 0         0 $plotted = TRUE;
4334             }
4335             } else { # Filled pie arc
4336 0 0       0 if ($degrees == $start_degrees) {
4337 0         0 push(@coords, $x, $y, $sx, $sy);
4338             } else {
4339 0         0 push(@coords, $sx, $sy);
4340             }
4341             }
4342 0         0 $ox = $sx;
4343 0         0 $oy = $sy;
4344             }
4345 0         0 $degrees += $granularity;
4346             } until ($degrees >= $end_degrees);
4347 0 0       0 if ($mode != ARC) {
4348 0 0       0 $params->{'filled'} = ($mode == PIE) ? TRUE : FALSE;
4349 0         0 $params->{'coordinates'} = \@coords;
4350 0         0 $self->polygon($params);
4351             }
4352 0         0 ($self->{'X'}, $self->{'Y'}) = ($sx, $sy);
4353              
4354             } else {
4355 0         0 my $w = ($radius * 2);
4356 0         0 my $pattern;
4357 0         0 my $saved = {
4358             'x' => $x - $radius,
4359             'y' => $y - $radius,
4360             'width' => $w,
4361             'height' => $w,
4362             'image' => '',
4363             };
4364 0         0 my $draw_mode;
4365             my $image;
4366 0         0 my $fill;
4367              
4368 0         0 eval { # Imager can crash.
4369 0         0 my $img = Imager->new(
4370             'xsize' => $w,
4371             'ysize' => $w,
4372             'raw_datachannels' => max(3, $bytes),
4373             'raw_storechannels' => max(3, $bytes),
4374             'channels' => max(3, $bytes),
4375             'raw_interleave' => 0,
4376             );
4377 0 0       0 unless ($self->{'DRAW_MODE'}) {
4378 0 0       0 if ($self->{'ACCELERATED'}) {
4379 0         0 $draw_mode = $self->{'DRAW_MODE'};
4380 0         0 $self->{'DRAW_MODE'} = MASK_MODE;
4381             } else {
4382 0         0 $saved = $self->blit_read($saved);
4383 0 0       0 $saved->{'image'} = $self->_convert_16_to_24($saved->{'image'}, RGB) if ($self->{'BITS'} == 16);
4384             $img->read(
4385             'xsize' => $w,
4386             'ysize' => $w,
4387             'raw_datachannels' => max(3, $bytes),
4388             'raw_storechannels' => max(3, $bytes),
4389             'channels' => max(3, $bytes),
4390             'raw_interleave' => 0,
4391 0         0 'data' => $saved->{'image'},
4392             'type' => 'raw',
4393             'allow_incomplete' => 1
4394             );
4395             }
4396             }
4397             my %p = (
4398             'x' => $radius,
4399             'y' => $radius,
4400             'd1' => $start_degrees,
4401             'd2' => $end_degrees,
4402             'r' => $radius,
4403             'filled' => TRUE,
4404 0         0 'color' => $self->{'IMAGER_FOREGROUND_COLOR'},
4405             );
4406 0 0       0 if (exists($params->{'hatch'})) {
    0          
    0          
4407             $fill = Imager::Fill->new(
4408             'hatch' => $params->{'hatch'} || 'dots16',
4409             'fg' => $self->{'IMAGER_FOREGROUND_COLOR'},
4410 0   0     0 'bg' => $self->{'IMAGER_BACKGROUND_COLOR'}
4411             );
4412 0         0 $p{'fill'} = $fill;
4413             } elsif (exists($params->{'texture'})) {
4414 0         0 $pattern = $self->_generate_fill($w, $w, undef, $params->{'texture'});
4415 0 0       0 $pattern = $self->_convert_16_to_24($pattern, RGB) if ($self->{'BITS'} == 16);
4416 0         0 $image = Imager->new(
4417             'xsize' => $w,
4418             'ysize' => $w,
4419             'raw_datachannels' => max(3, $bytes),
4420             'raw_storechannels' => max(3, $bytes),
4421             'raw_interleave' => 0,
4422             );
4423 0         0 $image->read(
4424             'xsize' => $w,
4425             'ysize' => $w,
4426             'raw_datachannels' => max(3, $bytes),
4427             'raw_storechannels' => max(3, $bytes),
4428             'raw_interleave' => 0,
4429             'data' => $pattern,
4430             'type' => 'raw',
4431             'allow_incomplete' => 1
4432             );
4433 0         0 $p{'fill'}->{'image'} = $image;
4434             } elsif (exists($params->{'gradient'})) {
4435 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
4436 0   0     0 $pattern = $self->_generate_fill($w, $w, $params->{'gradient'}->{'colors'}, $params->{'gradient'}->{'direction'} || 'vertical');
4437             } else {
4438             $pattern = $self->_generate_fill(
4439             $w, $w,
4440             {
4441             'red' => [$params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}],
4442             'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
4443             'blue' => [$params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}],
4444             'alpha' => (exists($params->{'gradient'}->{'start'}->{'alpha'})) ? [$params->{'gradient'}->{'start'}->{'alpha'},$params->{'gradient'}->{'end'}->{'alpha'}] : [$self->{'COLOR_ALPHA'},$self->{'COLOR_ALPHA'}],
4445             },
4446 0 0 0     0 $params->{'gradient'}->{'direction'} || 'vertical'
4447             );
4448             }
4449 0 0       0 $pattern = $self->_convert_16_to_24($pattern, RGB) if ($self->{'BITS'} == 16);
4450 0         0 $image = Imager->new(
4451             'xsize' => $w,
4452             'ysize' => $w,
4453             'raw_datachannels' => max(3, $bytes),
4454             'raw_storechannels' => max(3, $bytes),
4455             'raw_interleave' => 0,
4456             );
4457 0         0 $image->read(
4458             'xsize' => $w,
4459             'ysize' => $w,
4460             'raw_datachannels' => max(3, $bytes),
4461             'raw_storechannels' => max(3, $bytes),
4462             'raw_interleave' => 0,
4463             'data' => $pattern,
4464             'type' => 'raw',
4465             'allow_incomplete' => 1
4466             );
4467 0         0 $p{'fill'}->{'image'} = $image;
4468             }
4469 0         0 $img->arc(%p);
4470             $img->write(
4471             'type' => 'raw',
4472             'datachannels' => max(3, $bytes),
4473             'storechannels' => max(3, $bytes),
4474             'interleave' => 0,
4475 0         0 'data' => \$saved->{'image'},
4476             );
4477 0 0       0 $saved->{'image'} = $self->_convert_24_to_16($saved->{'image'}, RGB) if ($self->{'BITS'} == 16);
4478             };
4479 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
4480 0         0 $self->blit_write($saved);
4481 0 0       0 $self->{'DRAW_MODE'} = $draw_mode if (defined($draw_mode));
4482             }
4483             }
4484              
4485             sub arc {
4486             =head2 arc
4487              
4488             Draws an arc of a circle at point x,y. This is an alias to draw_arc above, but no mode parameter needed.
4489              
4490             =over 4
4491              
4492             x = x of center of circle
4493              
4494             y = y of center of circle
4495              
4496             radius = radius of circle
4497              
4498             start_degrees = starting point, in degrees, of arc
4499              
4500             end_degrees = ending point, in degrees, of arc
4501              
4502             granularity = This is used for accuracy in drawing
4503             the arc. The smaller the number, the
4504             more accurate the arc is drawn, but it
4505             is also slower. Values between 0.1
4506             and 0.01 are usually good. Valid values
4507             are any positive floating point number
4508             down to 0.0001.
4509              
4510             $fb->arc({
4511             'x' => 100,
4512             'y' => 100,
4513             'radius' => 100,
4514             'start_degrees' => -40,
4515             'end_degrees' => 80,
4516             'granularity => .05,
4517             });
4518              
4519             =back
4520              
4521             * This is not affected by the Acceleration setting
4522              
4523             =cut
4524              
4525 0     0 0 0 my $self = shift;
4526 0         0 my $params = shift;
4527              
4528 0         0 $params->{'mode'} = ARC;
4529 0         0 $self->draw_arc($params);
4530             }
4531              
4532             sub filled_pie {
4533             =head2 filled_pie
4534              
4535             Draws a filled pie wedge at point x,y. This is an alias to draw_arc above, but no mode parameter needed.
4536              
4537             =over 4
4538              
4539             x = x of center of circle
4540              
4541             y = y of center of circle
4542              
4543             radius = radius of circle
4544              
4545             start_degrees = starting point, in degrees, of arc
4546              
4547             end_degrees = ending point, in degrees, of arc
4548              
4549             granularity = This is used for accuracy in drawing
4550             the arc. The smaller the number, the
4551             more accurate the arc is drawn, but it
4552             is also slower. Values between 0.1
4553             and 0.01 are usually good. Valid values
4554             are any positive floating point number
4555             down to 0.0001.
4556              
4557             $fb->filled_pie({
4558             'x' => 100,
4559             'y' => 100,
4560             'radius' => 100,
4561             'start_degrees' => -40,
4562             'end_degrees' => 80,
4563             'granularity' => .05,
4564             'gradient' => { # optional
4565             'direction' => 'horizontal', # or vertical
4566             'colors' => { # 2 to any number of transitions allowed
4567             'red' => [255,255,0], # Red to yellow to cyan
4568             'green' => [0,255,255],
4569             'blue' => [0,0,255],
4570             'alpha' => [255,255,255],
4571             }
4572             },
4573             'texture' => { # Same as what blit_read or load_image returns
4574             'width' => 320,
4575             'height' => 240,
4576             'image' => $raw_image_data
4577             },
4578             'hatch' => 'hatchname' # The exported array @HATCHES contains
4579             # the names of all the hatches
4580             });
4581              
4582             =back
4583              
4584             * This is affected by the Acceleration setting
4585              
4586             =cut
4587              
4588 0     0 0 0 my $self = shift;
4589 0         0 my $params = shift;
4590              
4591 0         0 $params->{'mode'} = PIE;
4592 0         0 $self->draw_arc($params);
4593             }
4594              
4595             sub poly_arc {
4596             =head2 poly_arc
4597              
4598             Draws a poly arc of a circle at point x,y. This is an alias to draw_arc above, but no mode parameter needed.
4599              
4600             =over 4
4601              
4602             x = x of center of circle
4603              
4604             y = y of center of circle
4605              
4606             radius = radius of circle
4607              
4608             start_degrees = starting point, in degrees, of arc
4609              
4610             end_degrees = ending point, in degrees, of arc
4611              
4612             granularity = This is used for accuracy in drawing
4613             the arc. The smaller the number, the
4614             more accurate the arc is drawn, but it
4615             is also slower. Values between 0.1
4616             and 0.01 are usually good. Valid values
4617             are any positive floating point number
4618             down to 0.0001.
4619              
4620             $fb->poly_arc({
4621             'x' => 100,
4622             'y' => 100,
4623             'radius' => 100,
4624             'start_degrees' => -40,
4625             'end_degrees' => 80,
4626             'granularity' => .05,
4627             });
4628              
4629             =back
4630              
4631             * This is not affected by the Acceleration setting
4632              
4633             =cut
4634              
4635 0     0 0 0 my $self = shift;
4636 0         0 my $params = shift;
4637              
4638 0         0 $params->{'mode'} = POLY_ARC;
4639 0         0 $self->draw_arc($params);
4640             }
4641              
4642             sub ellipse {
4643             =head2 ellipse
4644              
4645             Draw an ellipse at center position x,y with XRadius, YRadius. Either a filled ellipse or outline is drawn based on the value of $filled. The optional factor value varies from the default 1 to change the look and nature of the output.
4646              
4647             =over 4
4648              
4649             $fb->ellipse({
4650             'x' => 200, # Horizontal center
4651             'y' => 250, # Vertical center
4652             'xradius' => 50,
4653             'yradius' => 100,
4654             'factor' => 1, # Anything other than 1 has funkiness
4655             'pixel_size' => 4, # optional
4656             'filled' => 1, # optional
4657              
4658             ## Only one of the following may be used
4659              
4660             'gradient' => { # optional, but 'filled' must be set
4661             'direction' => 'horizontal', # or vertical
4662             'colors' => { # 2 to any number of transitions allowed
4663             'red' => [255,255,0], # Red to yellow to cyan
4664             'green' => [0,255,255],
4665             'blue' => [0,0,255],
4666             'alpha' => [255,255,255],
4667             }
4668             }
4669             'texture' => { # Same format blit_read or load_image uses.
4670             'width' => 320,
4671             'height' => 240,
4672             'image' => $raw_image_data
4673             },
4674             'hatch' => 'hatchname' # The exported array @HATCHES contains
4675             # the names of all the hatches
4676             });
4677              
4678             =back
4679              
4680             * This is not affected by the Acceleration setting
4681              
4682             =cut
4683              
4684             # The routine even works properly for XOR mode when filled ellipses are drawn as well. This was solved by drawing only if the X or Y position changed.
4685 0     0 0 0 my $self = shift;
4686 0         0 my $params = shift;
4687              
4688 0         0 my $cx = int($params->{'x'});
4689 0         0 my $cy = int($params->{'y'});
4690 0   0     0 my $XRadius = int($params->{'xradius'} || 1);
4691 0   0     0 my $YRadius = int($params->{'yradius'} || 1);
4692              
4693 0 0       0 $XRadius = 1 if ($XRadius < 1);
4694 0 0       0 $YRadius = 1 if ($YRadius < 1);
4695              
4696 0   0     0 my $filled = int($params->{'filled'} || 0);
4697 0   0     0 my $fact = $params->{'factor'} || 1;
4698 0   0     0 my $size = int($params->{'pixel_size'} || 1);
4699 0 0       0 $size = 1 if ($filled);
4700              
4701 0         0 my ($old_cyy, $old_cy_y) = (0, 0);
4702 0 0       0 if ($fact == 0) { # We don't allow zero values for this
4703 0         0 $fact = 1;
4704             }
4705 0         0 my $xsq = $XRadius * $XRadius;
4706 0         0 my $ysq = $YRadius * $YRadius;
4707 0         0 my $TwoASquare = (2 * $xsq) * $fact;
4708 0         0 my $TwoBSquare = (2 * $ysq) * $fact;
4709 0         0 my $x = $XRadius;
4710 0         0 my $y = 0;
4711 0         0 my $XChange = $ysq * (1 - (2 * $XRadius));
4712 0         0 my $YChange = $xsq;
4713 0         0 my $EllipseError = 0;
4714 0         0 my $StoppingX = $TwoBSquare * $XRadius;
4715 0         0 my $StoppingY = 0;
4716 0 0       0 my $history_on = (exists($self->{'history'})) ? TRUE : FALSE;
4717              
4718             # The history prevents double drawing
4719 0 0 0     0 $self->{'history'} = {} unless ($history_on || !$filled || $size > 1);
      0        
4720 0         0 my ($red, $green, $blue, $pattern, $plen, @rc, @gc, @bc);
4721 0         0 my $gradient = FALSE;
4722 0         0 my $saved = $self->{'RAW_FOREGROUND_COLOR'};
4723 0         0 my $xdiameter = $XRadius * 2;
4724 0         0 my $ydiameter = $YRadius * 2;
4725 0         0 my $bytes = $self->{'BYTES'};
4726 0 0       0 if (exists($params->{'gradient'})) {
    0          
    0          
4727 0 0       0 if ($params->{'gradient'}->{'direction'} !~ /vertical/i) {
4728 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
4729 0         0 $pattern = $self->_generate_fill($xdiameter, $ydiameter, $params->{'gradient'}->{'colors'}, 'horizontal');
4730             } else {
4731             $pattern = $self->_generate_fill(
4732             $xdiameter,
4733             $ydiameter,
4734             {
4735             'red' => [$params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}],
4736             'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
4737             'blue' => [$params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}],
4738 0 0       0 'alpha' => (exists($params->{'gradient'}->{'start'}->{'alpha'})) ? [$params->{'gradient'}->{'start'}->{'alpha'},$params->{'gradient'}->{'end'}->{'alpha'}] : [$self->{'COLOR_ALPHA'},$self->{'COLOR_ALPHA'}],
4739             },
4740             'horizontal'
4741             );
4742             }
4743 0         0 $plen = length($pattern);
4744 0         0 $gradient = 2;
4745             } else {
4746 0         0 my $ydiameter = $YRadius * 2;
4747 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
4748 0         0 @rc = multi_gradient($ydiameter, @{ $params->{'gradient'}->{'colors'}->{'red'} });
  0         0  
4749 0         0 @gc = multi_gradient($ydiameter, @{ $params->{'gradient'}->{'colors'}->{'green'} });
  0         0  
4750 0         0 @bc = multi_gradient($ydiameter, @{ $params->{'gradient'}->{'colors'}->{'blue'} });
  0         0  
4751 0 0       0 if (exists($params->{'gradient'}->{'colors'}->{'alpha'})) {
4752 0         0 @ac = multi_gradient($ydiameter, @{ $params->{'gradient'}->{'colors'}->{'alpha'} });
  0         0  
4753             } else {
4754 0         0 @ac = map {$_ = $self->{'COLOR_ALPHA'}} (1..(scalar(@bc)));
  0         0  
4755             }
4756             } else {
4757 0         0 @rc = gradient($params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}, $ydiameter);
4758 0         0 @gc = gradient($params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}, $ydiameter);
4759 0         0 @bc = gradient($params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}, $ydiameter);
4760 0 0       0 if (exists($params->{'gradient'}->{'start'}->{'alpha'})) {
4761 0         0 @ac = gradient($params->{'gradient'}->{'start'}->{'alpha'}, $params->{'gradient'}->{'end'}->{'alpha'}, $ydiameter);
4762             } else {
4763 0         0 @ac = map {$_ = $self->{'COLOR_ALPHA'}} (1..2);
  0         0  
4764             }
4765             }
4766 0         0 $gradient = 1;
4767             }
4768             } elsif (exists($params->{'texture'})) {
4769 0         0 $pattern = $self->_generate_fill($xdiameter, $ydiameter, undef, $params->{'texture'});
4770 0         0 $gradient = 2;
4771             } elsif (exists($params->{'hatch'})) {
4772 0         0 $pattern = $self->_generate_fill($xdiameter, $ydiameter, undef, $params->{'hatch'});
4773 0         0 $gradient = 2;
4774             }
4775              
4776 0         0 my $left = $cx - $XRadius;
4777 0         0 while ($StoppingX >= $StoppingY) {
4778 0         0 my $cxx = int($cx + $x);
4779 0         0 my $cx_x = int($cx - $x);
4780 0         0 my $cyy = int($cy + $y);
4781 0         0 my $cy_y = int($cy - $y);
4782 0         0 my $rpy = $YRadius + $y;
4783 0         0 my $rmy = $YRadius - $y;
4784              
4785 0 0       0 if ($filled) {
4786 0 0       0 if ($cyy <=> $old_cyy) {
4787 0 0       0 if ($gradient == 2) {
4788 0         0 my $wd = max($cx_x, $cxx) - min($cxx, $cx_x);
4789 0         0 $self->blit_write({ 'x' => min($cxx, $cx_x), 'y' => $cyy, 'width' => $wd, 'height' => 1, 'image' => substr($pattern, $bytes * (min($cxx, $cx_x) - $left) + ($rpy * ($xdiameter * $bytes)), $bytes * ($wd)) });
4790             } else {
4791 0 0       0 if ($gradient) {
4792 0         0 $self->set_color({ 'red' => $rc[$rpy], 'green' => $gc[$rpy], 'blue' => $bc[$rpy] });
4793             }
4794 0         0 $self->line({ 'x' => $cxx, 'y' => $cyy, 'xx' => $cx_x, 'yy' => $cyy });
4795             }
4796 0         0 $old_cyy = $cyy;
4797             }
4798 0 0 0     0 if (($cy_y <=> $old_cy_y) && ($cyy <=> $cy_y)) {
4799 0 0       0 if ($gradient == 2) {
4800 0         0 my $wd = max($cx_x, $cxx) - min($cxx, $cx_x);
4801 0         0 $self->blit_write({ 'x' => min($cxx, $cx_x), 'y' => $cy_y, 'width' => $wd, 'height' => 1, 'image' => substr($pattern, $bytes * (min($cxx, $cx_x) - $left) + ($rmy * ($xdiameter * $bytes)), $bytes * ($wd)) });
4802             } else {
4803 0 0       0 if ($gradient) {
4804 0         0 $self->set_color({ 'red' => $rc[$rmy], 'green' => $gc[$rmy], 'blue' => $bc[$rmy] });
4805             }
4806 0         0 $self->line({ 'x' => $cx_x, 'y' => $cy_y, 'xx' => $cxx, 'yy' => $cy_y });
4807             }
4808 0         0 $old_cy_y = $cy_y;
4809             }
4810             } else {
4811 0         0 $self->plot({ 'x' => $cxx, 'y' => $cyy, 'pixel_size' => $size });
4812 0         0 $self->plot({ 'x' => $cx_x, 'y' => $cyy, 'pixel_size' => $size });
4813 0 0       0 $self->plot({ 'x' => $cx_x, 'y' => $cy_y, 'pixel_size' => $size }) if (int($cyy) <=> int($cy_y));
4814 0 0       0 $self->plot({ 'x' => $cxx, 'y' => $cy_y, 'pixel_size' => $size }) if (int($cyy) <=> int($cy_y));
4815             }
4816 0         0 $y++;
4817 0         0 $StoppingY += $TwoASquare;
4818 0         0 $EllipseError += $YChange;
4819 0         0 $YChange += $TwoASquare;
4820 0 0       0 if ((($EllipseError * 2) + $XChange) > 0) {
4821 0         0 $x--;
4822 0         0 $StoppingX -= $TwoBSquare;
4823 0         0 $EllipseError += $XChange;
4824 0         0 $XChange += $TwoBSquare;
4825             }
4826             }
4827 0         0 $x = 0;
4828 0         0 $y = $YRadius;
4829 0         0 $XChange = $ysq;
4830 0         0 $YChange = $xsq * (1 - 2 * $YRadius);
4831 0         0 $EllipseError = 0;
4832 0         0 $StoppingX = 0;
4833 0         0 $StoppingY = $TwoASquare * $YRadius;
4834              
4835 0         0 while ($StoppingX <= $StoppingY) {
4836 0         0 my $cxx = int($cx + $x);
4837 0         0 my $cx_x = int($cx - $x);
4838 0         0 my $cyy = int($cy + $y);
4839 0         0 my $cy_y = int($cy - $y);
4840 0         0 my $rpy = $YRadius + $y;
4841 0         0 my $rmy = $YRadius - $y;
4842 0 0       0 if ($filled) {
4843 0 0       0 if ($cyy <=> $old_cyy) {
4844 0 0       0 if ($gradient == 2) {
4845 0         0 my $wd = max($cx_x, $cxx) - min($cxx, $cx_x);
4846 0         0 $self->blit_write({ 'x' => min($cxx, $cx_x), 'y' => $cyy, 'width' => $wd, 'height' => 1, 'image' => substr($pattern, $bytes * (min($cxx, $cx_x) - $left) + ($rpy * ($xdiameter * $bytes)), $bytes * ($wd)) });
4847             } else {
4848 0 0       0 if ($gradient) {
4849 0         0 $self->set_color({ 'red' => $rc[$rpy], 'green' => $gc[$rpy], 'blue' => $bc[$rpy] });
4850             }
4851 0         0 $self->line({ 'x' => $cxx, 'y' => $cyy, 'xx' => $cx_x, 'yy' => $cyy });
4852             }
4853 0         0 $old_cyy = $cyy;
4854             }
4855 0 0 0     0 if (($cy_y <=> $old_cy_y) && ($cyy <=> $cy_y)) {
4856 0 0       0 if ($gradient == 2) {
4857 0         0 my $wd = max($cx_x, $cxx) - min($cxx, $cx_x);
4858 0         0 $self->blit_write({ 'x' => min($cxx, $cx_x), 'y' => $cy_y, 'width' => $wd, 'height' => 1, 'image' => substr($pattern, $bytes * (min($cxx, $cx_x) - $left) + ($rmy * ($xdiameter * $bytes)), $bytes * ($wd)) });
4859             } else {
4860 0 0       0 if ($gradient) {
4861 0         0 $self->set_color({ 'red' => $rc[$rmy], 'green' => $gc[$rmy], 'blue' => $bc[$rmy] });
4862             }
4863 0         0 $self->line({ 'x' => $cx_x, 'y' => $cy_y, 'xx' => $cxx, 'yy' => $cy_y });
4864             }
4865 0         0 $old_cy_y = $cy_y;
4866             }
4867             } else {
4868 0         0 $self->plot({ 'x' => $cxx, 'y' => $cyy, 'pixel_size' => $size });
4869 0 0       0 $self->plot({ 'x' => $cx_x, 'y' => $cyy, 'pixel_size' => $size }) if (int($cxx) <=> int($cx_x));
4870 0 0       0 $self->plot({ 'x' => $cx_x, 'y' => $cy_y, 'pixel_size' => $size }) if (int($cxx) <=> int($cx_x));
4871 0         0 $self->plot({ 'x' => $cxx, 'y' => $cy_y, 'pixel_size' => $size });
4872             }
4873 0         0 $x++;
4874 0         0 $StoppingX += $TwoBSquare;
4875 0         0 $EllipseError += $XChange;
4876 0         0 $XChange += $TwoBSquare;
4877 0 0       0 if ((($EllipseError * 2) + $YChange) > 0) {
4878 0         0 $y--;
4879 0         0 $StoppingY -= $TwoASquare;
4880 0         0 $EllipseError += $YChange;
4881 0         0 $YChange += $TwoASquare;
4882             }
4883             }
4884 0 0 0     0 delete($self->{'history'}) if (exists($self->{'history'}) && !$history_on);
4885 0         0 $self->{'RAW_FOREGROUND_COLOR'} = $saved;
4886             }
4887              
4888             sub circle {
4889             =head2 circle
4890              
4891             Draws a circle at point x,y, with radius 'radius'. It can be an outline, solid filled, or gradient filled. Outlined circles can have any pixel size.
4892              
4893             =over 4
4894              
4895             $fb->circle({
4896             'x' => 300, # Horizontal center
4897             'y' => 300, # Vertical center
4898             'radius' => 100,
4899             'filled' => 1, # optional
4900             'gradient' => { # optional
4901             'direction' => 'horizontal', # or vertical
4902             'colors' => { # 2 to any number of transitions allowed
4903             'red' => [255,255,0], # Red to yellow to cyan
4904             'green' => [0,255,255],
4905             'blue' => [0,0,255],
4906             'alpha' => [255,255,255],
4907             }
4908             },
4909             'texture' => { # Same as what blit_read or load_image returns
4910             'width' => 320,
4911             'height' => 240,
4912             'image' => $raw_image_data
4913             },
4914             'hatch' => 'hatchname' # The exported array @HATCHES contains
4915             # the names of all the hatches
4916             });
4917              
4918             =back
4919              
4920             * This is affected by the Acceleration setting
4921              
4922             =cut
4923              
4924             # This also doubles as the rounded box routine.
4925              
4926 0     0 0 0 my $self = shift;
4927 0         0 my $params = shift;
4928              
4929 0         0 my $x0 = int($params->{'x'});
4930 0         0 my $y0 = int($params->{'y'});
4931 0   0     0 my $x1 = int($params->{'xx'}) || $x0;
4932 0   0     0 my $y1 = int($params->{'yy'}) || $y0;
4933 0   0     0 my $bx = int($params->{'bx'}) || 0;
4934 0   0     0 my $by = int($params->{'by'}) || 0;
4935 0   0     0 my $bxx = int($params->{'bxx'}) || 1;
4936 0   0     0 my $byy = int($params->{'byy'}) || 1;
4937 0         0 my $r = int($params->{'radius'});
4938 0   0     0 my $filled = $params->{'filled'} || FALSE;
4939 0 0       0 my $gradient = (defined($params->{'gradient'})) ? TRUE : FALSE;
4940 0   0     0 my $size = $params->{'pixel_size'} || 1;
4941 0         0 my $start = $y0 - $r;
4942 0         0 my $x = $r;
4943 0         0 my $y = 0;
4944 0         0 my $decisionOver2 = 1 - $x;
4945 0         0 my (@rc, @gc, @bc, @ac);
4946              
4947 0 0       0 ($x0, $x1) = ($x1, $x0) if ($x0 > $x1);
4948 0 0       0 ($y0, $y1) = ($y1, $y0) if ($y0 > $y1);
4949 0         0 my $_x = $x0 - $r;
4950 0         0 my $_xx = $x1 + $r;
4951 0         0 my $_y = $y0 - $r;
4952 0         0 my $_yy = $y1 + $r;
4953 0         0 my $xstart = $_x;
4954              
4955 0         0 my @coords;
4956 0         0 my $saved = $self->{'RAW_FOREGROUND_COLOR'};
4957 0         0 my $W = $r * 2;
4958 0         0 my $count = $W + abs($y1 - $y0);
4959 0         0 my $pattern;
4960 0         0 my $wdth = $_xx - $_x;
4961 0         0 my $hgth = $_yy - $_y;
4962 0         0 my $bytes = $self->{'BYTES'};
4963 0         0 my $plen = $wdth * $bytes;
4964 0         0 $self->{'history'} = {};
4965              
4966 0 0       0 if ($gradient) {
    0          
    0          
4967 0 0 0     0 $W = $bxx - $bx unless ($x0 == $x1 && $y0 == $y1);
4968 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
4969 0         0 $pattern = $self->_generate_fill($wdth, $hgth, $params->{'gradient'}->{'colors'}, $params->{'gradient'}->{'direction'});
4970             } else {
4971             $pattern = $self->_generate_fill(
4972             $wdth, $hgth,
4973             {
4974             'red' => [$params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}],
4975             'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
4976             'blue' => [$params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}],
4977             'alpha' => (exists($params->{'gradient'}->{'start'}->{'alpha'})) ? [$params->{'gradient'}->{'start'}->{'alpha'},$params->{'gradient'}->{'end'}->{'alpha'}] : [$self->{'COLOR_ALPHA'},$self->{'COLOR_ALPHA'}],
4978             },
4979 0 0       0 $params->{'gradient'}->{'direction'}
4980             );
4981             }
4982 0         0 $plen = $wdth * $bytes;
4983 0         0 $gradient = 2;
4984             } elsif (exists($params->{'texture'})) {
4985 0         0 $pattern = $self->_generate_fill($wdth, $hgth, undef, $params->{'texture'});
4986 0         0 $gradient = 2;
4987             } elsif (exists($params->{'hatch'})) {
4988 0         0 $pattern = $self->_generate_fill($wdth, $hgth, undef, $params->{'hatch'});
4989 0         0 $gradient = 2;
4990             }
4991 0         0 my ($ymy, $lymy, $ymx, $lymx, $ypy, $lypy, $ypx, $lypx, $xmy, $xmx, $xpy, $xpx);
4992 0         0 while ($x >= ($y - 1)) {
4993 0         0 $ymy = $y0 - $y; # Top
4994 0         0 $ymx = $y0 - $x;
4995 0         0 $ypy = $y1 + $y; # Bottom
4996 0         0 $ypx = $y1 + $x;
4997 0         0 $xmy = $x0 - $y; # Left
4998 0         0 $xmx = $x0 - $x;
4999 0         0 $xpy = $x1 + $y; # Right
5000 0         0 $xpx = $x1 + $x;
5001              
5002 0 0       0 if ($filled) {
5003 0         0 my $ymy_i = $ymy - $start;
5004 0         0 my $ymx_i = $ymx - $start;
5005 0         0 my $ypy_i = $ypy - $start;
5006 0         0 my $ypx_i = $ypx - $start;
5007              
5008 0 0       0 if ($gradient == 2) {
    0          
5009 0         0 my $fxmy = $xmy;
5010 0         0 my $fxmx = $xmx;
5011 0         0 my $fxpy = $xpy;
5012 0         0 my $fxpx = $xpx;
5013              
5014             # Top
5015 0         0 my $fwd = $fxpx - $fxmx;
5016 0         0 my $wd = $xpx - $xmx;
5017 0 0 0     0 if ($ymy != $lymy && $ymy != $lymx && $ymy != $lypx && $ymy != $lypy) {
      0        
      0        
5018 0         0 ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmx, $ymy, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ymy_i * $plen), $fwd * $bytes));
5019 0         0 $self->blit_write($params);
5020             }
5021              
5022 0         0 $fwd = $fxpy - $fxmy;
5023 0         0 $wd = $xpy - $xmy;
5024 0 0 0     0 if ($ymx != $lymx && $ymx != $lymy && $ymx != $lypx && $ymx != $lypy) {
      0        
      0        
5025 0         0 ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmy, $ymx, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ymx_i * $plen), $fwd * $bytes));
5026 0         0 $self->blit_write($params);
5027             }
5028              
5029             # Bottom
5030 0         0 $fwd = $fxpx - $fxmx;
5031 0         0 $wd = $xpx - $xmx;
5032 0 0 0     0 if ($ypy != $lypy && $ypy != $lypx && $ypy != $lymy && $ypy != $lymx) {
      0        
      0        
5033 0         0 ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmx, $ypy, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ypy_i * $plen), $fwd * $bytes));
5034 0         0 $self->blit_write($params);
5035             }
5036              
5037 0         0 $fwd = $fxpy - $fxmy;
5038 0         0 $wd = $xpy - $xmy;
5039 0 0 0     0 if ($ypx != $lypx && $ypx != $lypy && $ypx != $lymx && $ypx != $lymy) {
      0        
      0        
5040 0         0 ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmy, $ypx, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ypx_i * $plen), $fwd * $bytes));
5041 0         0 $self->blit_write($params);
5042             }
5043             } elsif ($gradient) {
5044             # Top
5045 0 0 0     0 if ($ymy != $lymy && $ymy != $lymx && $ymy != $lypx && $ymy != $lypy) {
      0        
      0        
5046 0         0 $self->set_color({ 'red' => $rc[$ymy_i], 'green' => $gc[$ymy_i], 'blue' => $bc[$ymy_i] });
5047 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ymy, $xpx, $ymy);
5048 0         0 $self->line($params);
5049             }
5050 0 0 0     0 if ($ymx != $lymx && $ymx != $lymy && $ymx != $lypx && $ymx != $lypy) {
      0        
      0        
5051 0         0 $self->set_color({ 'red' => $rc[$ymx_i], 'green' => $gc[$ymx_i], 'blue' => $bc[$ymx_i] });
5052 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ymx, $xpy, $ymx);
5053 0         0 $self->line($params);
5054             }
5055              
5056             # Bottom
5057 0 0 0     0 if ($ypy != $lypy && $ypy != $lypx && $ypy != $lymy && $ypy != $lymx) {
      0        
      0        
5058 0         0 $self->set_color({ 'red' => $rc[$ypy_i], 'green' => $gc[$ypy_i], 'blue' => $bc[$ypy_i] });
5059 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ypy, $xpx, $ypy);
5060 0         0 $self->line($params);
5061             }
5062 0 0 0     0 if ($ypx != $lypx && $ypx != $lypy && $ypx != $lymx && $ypx != $lymy) {
      0        
      0        
5063 0         0 $self->set_color({ 'red' => $rc[$ypx_i], 'green' => $gc[$ypx_i], 'blue' => $bc[$ypx_i] });
5064 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ypx, $xpy, $ypx);
5065 0         0 $self->line($params);
5066             }
5067             } else {
5068             # Top
5069 0 0 0     0 if ($ymy != $lymy && $ymy != $lymx && $ymy != $lypx && $ymy != $lypy) {
      0        
      0        
5070 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ymy, $xpx, $ymy);
5071 0         0 $self->line($params);
5072             }
5073 0 0 0     0 if ($ymx != $lymx && $ymx != $lymy && $ymx != $lypx && $ymx != $lypy) {
      0        
      0        
5074 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ymx, $xpy, $ymx);
5075 0         0 $self->line($params);
5076             }
5077              
5078             # Bottom
5079 0 0 0     0 if ($ypy != $lypy && $ypy != $lypx && $ypy != $lymy && $ypy != $lymx) {
      0        
      0        
5080 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ypy, $xpx, $ypy);
5081 0         0 $self->line($params);
5082             }
5083 0 0 0     0 if ($ypx != $lypx && $ypx != $lypy && $ypx != $lymx && $ypx != $lymy) {
      0        
      0        
5084 0         0 ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ypx, $xpy, $ypx);
5085 0         0 $self->line($params);
5086             }
5087             }
5088 0         0 $lymy = $ymy;
5089 0         0 $lymx = $ymx;
5090 0         0 $lypy = $ypy;
5091 0         0 $lypx = $ypx;
5092             } else {
5093             # Top left
5094 0         0 ($params->{'x'}, $params->{'y'}) = ($xmx, $ymy);
5095 0         0 $self->plot($params);
5096 0         0 ($params->{'x'}, $params->{'y'}) = ($xmy, $ymx);
5097 0         0 $self->plot($params);
5098              
5099             # Top right
5100 0         0 ($params->{'x'}, $params->{'y'}) = ($xpx, $ymy);
5101 0         0 $self->plot($params);
5102 0         0 ($params->{'x'}, $params->{'y'}) = ($xpy, $ymx);
5103 0         0 $self->plot($params);
5104              
5105             # Bottom right
5106 0         0 ($params->{'x'}, $params->{'y'}) = ($xpx, $ypy);
5107 0         0 $self->plot($params);
5108 0         0 ($params->{'x'}, $params->{'y'}) = ($xpy, $ypx);
5109 0         0 $self->plot($params);
5110              
5111             # Bottom left
5112 0         0 ($params->{'x'}, $params->{'y'}) = ($xmx, $ypy);
5113 0         0 $self->plot($params);
5114 0         0 ($params->{'x'}, $params->{'y'}) = ($xmy, $ypx);
5115 0         0 $self->plot($params);
5116              
5117 0         0 $lymy = $ymy;
5118 0         0 $lymx = $ymx;
5119 0         0 $lypy = $ypy;
5120 0         0 $lypx = $ypx;
5121             }
5122 0         0 $y++;
5123 0 0       0 if ($decisionOver2 <= 0) {
5124 0         0 $decisionOver2 += 2 * $y + 1;
5125             } else {
5126 0         0 $x--;
5127 0         0 $decisionOver2 += 2 * ($y - $x) + 1;
5128             }
5129             }
5130 0 0 0     0 unless ($x0 == $x1 && $y0 == $y1) {
5131 0 0       0 if ($filled) {
5132 0 0       0 if ($gradient == 2) {
    0          
5133 0         0 my $x = $_x;
5134 0         0 my $y = $y0;
5135 0         0 my $width = $wdth;
5136 0         0 my $height = $y1 - $y0;
5137 0         0 my $index = ($y0 - $start) * $plen;
5138 0         0 my $sz = $plen * $height;
5139 0 0 0     0 $self->blit_write({ 'x' => $x, 'y' => $y, 'width' => $width, 'height' => $height, 'image' => substr($pattern, $index, $sz) }) if ($height && $width);
5140             } elsif ($gradient) {
5141 0         0 foreach my $v ($y0 .. $y1) {
5142 0         0 my $offset = $v - $start;
5143 0         0 $self->set_color({ 'red' => $rc[$offset], 'green' => $gc[$offset], 'blue' => $bc[$offset] });
5144 0         0 $self->line({ 'x' => $_x, 'y' => $v, 'xx' => $_xx, 'yy' => $v, 'pixel_size' => 1 });
5145             }
5146             } else {
5147 0         0 $self->{'RAW_FOREGROUND_COLOR'} = $saved;
5148 0         0 $self->box({ 'x' => $_x, 'y' => $y0, 'xx' => $_xx, 'yy' => $y1, 'filled' => 1 });
5149             }
5150             } else {
5151             # top
5152 0         0 $self->line({ 'x' => $x0, 'y' => $_y, 'xx' => $x1, 'yy' => $_y, 'pixel_size' => $size });
5153              
5154             # right
5155 0         0 $self->line({ 'x' => $_xx, 'y' => $y0, 'xx' => $_xx, 'yy' => $y1, 'pixel_size' => $size });
5156              
5157             # bottom
5158 0         0 $self->line({ 'x' => $x0, 'y' => $_yy, 'xx' => $x1, 'yy' => $_yy, 'pixel_size' => $size });
5159              
5160             # left
5161 0         0 $self->line({ 'x' => $_x, 'y' => $y0, 'xx' => $_x, 'yy' => $y1, 'pixel_size' => $size });
5162             }
5163             }
5164 0         0 $self->{'RAW_FOREGROUND_COLOR'} = $saved;
5165 0         0 delete($self->{'history'});
5166             }
5167              
5168             sub _fpart {
5169 0     0   0 return ((POSIX::modf(shift))[0]);
5170             }
5171              
5172             sub _rfpart {
5173 0     0   0 return (1 - _fpart(shift));
5174             }
5175              
5176             sub polygon {
5177             =head2 polygon
5178              
5179             Creates a polygon drawn in the foreground color value. The parameter 'coordinates' is a reference to an array of x,y values. The last x,y combination is connected automatically with the first to close the polygon. All x,y values are absolute, not relative.
5180              
5181             It is up to you to make sure the coordinates are "sane". Weird things can result from twisted or complex filled polygons.
5182              
5183             =over 4
5184              
5185             $fb->polygon({
5186             'coordinates' => [
5187             5,5,
5188             23,34,
5189             70,7
5190             ],
5191             'pixel_size' => 1, # optional
5192             'antialiased' => 1, # optional only for non-filled
5193             'filled' => 1, # optional
5194              
5195             ## Only one of the following, "filled" must be set
5196              
5197             'gradient' => { # optional
5198             'direction' => 'horizontal', # or vertical
5199             'colors' => { # 2 to any number of transitions allowed
5200             'red' => [255,255,0], # Red to yellow to cyan
5201             'green' => [0,255,255],
5202             'blue' => [0,0,255],
5203             'alpha' => [255,255,255],
5204             }
5205             },
5206             'texture' => { # Same as what blit_read or load_image returns
5207             'width' => 320,
5208             'height' => 240,
5209             'image' => $raw_image_data
5210             },
5211             'hatch' => 'hatchname' # The exported array @HATCHES contains
5212             # the names of all the hatches
5213             });
5214              
5215             =back
5216              
5217             * Filled polygons are affected by the acceleration setting.
5218              
5219             =cut
5220              
5221 0     0 0 0 my $self = shift;
5222 0         0 my $params = shift;
5223              
5224 0   0     0 my $size = int($params->{'pixel_size'} || 1);
5225 0   0     0 my $aa = $params->{'antialiased'} || 0;
5226 0 0       0 my $history_on = (exists($self->{'history'})) ? TRUE : FALSE;
5227              
5228 0 0       0 if ($params->{'filled'}) {
5229 0         0 $self->_fill_polygon($params);
5230             } else {
5231 0 0       0 $self->{'history'} = {} unless ($history_on);
5232 0         0 my @coords = @{ $params->{'coordinates'} };
  0         0  
5233 0         0 my ($xx, $yy) = (int(shift(@coords)), int(shift(@coords)));
5234 0         0 $self->plot({ 'x' => $xx, 'y' => $yy, 'pixel_size' => $size });
5235 0         0 while (scalar(@coords)) {
5236 0         0 my ($x, $y) = (int(shift(@coords)), int(shift(@coords)));
5237 0         0 $self->drawto({ 'x' => $x, 'y' => $y, 'pixel_size' => $size, 'antialiased' => $aa });
5238             }
5239 0         0 $self->drawto({ 'x' => $xx, 'y' => $yy, 'pixel_size' => $size, 'antialiased' => $aa });
5240 0 0       0 $self->plot({ 'x' => $xx, 'y' => $yy, 'pixel_size' => $size }) if ($self->{'DRAW_MODE'} == 1);
5241 0 0       0 delete($self->{'history'}) unless ($history_on);
5242             }
5243             }
5244              
5245             sub _point_in_polygon {
5246             # Does point x,y fall inside the polygon described in coordinates? Not yet used.
5247 0     0   0 my $self = shift;
5248 0         0 my $params = shift;
5249              
5250 0         0 my $poly_corners = (scalar(@{ $params->{'coordinates'} }) / 2);
  0         0  
5251 0         0 my ($x, $y) = (int($params->{'x'}), int($params->{'y'}));
5252 0         0 my $j = $poly_corners - 1;
5253 0         0 my $odd_nodes = FALSE;
5254              
5255 0         0 for (my $i = 0; $i < $poly_corners; $i += 2) {
5256 0         0 my ($ip, $jp) = ($i + 1, $j + 1);
5257 0 0 0     0 if (($params->{'coordinates'}->[$ip] < $y && $params->{'coordinates'}->[$jp] >= $y || $params->{'coordinates'}->[$jp] < $y && $params->{'coordinates'}->[$ip] >= $y) && ($params->{'coordinates'}->[$i] <= $x || $params->{'coordinates'}->[$j] <= $x)) {
      0        
      0        
5258 0         0 $odd_nodes ^= ($params->{'coordinates'}->[$i] + ($y - $params->{'coordinates'}->[$ip]) / ($params->{'coordinates'}->[$jp] - $params->{'coordinates'}->[$ip]) * ($params->{'coordinates'}->[$j] - $params->{'coordinates'}->[$i]) < $x);
5259             }
5260 0         0 $j = $i;
5261             }
5262 0         0 return ($odd_nodes);
5263             }
5264              
5265             sub _fill_polygon {
5266 0     0   0 my $self = shift;
5267 0         0 my $params = shift;
5268 0         0 my $bytes = $self->{'BYTES'};
5269              
5270 0         0 my $points = [];
5271 0         0 my $left = 0;
5272 0         0 my $right = 0;
5273 0         0 my $top = 0;
5274 0         0 my $bottom = 0;
5275 0         0 my $fill;
5276 0         0 while (scalar(@{ $params->{'coordinates'} })) {
  0         0  
5277 0         0 my $x = int(shift(@{ $params->{'coordinates'} })) - $self->{'X_CLIP'}; # Compensate for the smaller area in Imager
  0         0  
5278 0         0 my $y = int(shift(@{ $params->{'coordinates'} })) - $self->{'Y_CLIP'};
  0         0  
5279 0         0 $left = min($left, $x);
5280 0         0 $right = max($right, $x);
5281 0         0 $top = min($top, $y);
5282 0         0 $bottom = max($bottom, $y);
5283 0         0 push(@{$points}, [$x, $y]);
  0         0  
5284             }
5285 0         0 my $width = abs($right - $left);
5286 0         0 my $height = abs($bottom - $top);
5287 0         0 my $pattern;
5288 0 0       0 if (exists($params->{'gradient'})) {
    0          
5289 0   0     0 $params->{'gradient'}->{'direction'} ||= 'vertical';
5290 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
5291 0         0 $pattern = $self->_generate_fill($width, $height, $params->{'gradient'}->{'colors'}, $params->{'gradient'}->{'direction'});
5292             } else {
5293             $pattern = $self->_generate_fill(
5294             $width, $height,
5295             {
5296             'red' => [$params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}],
5297             'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
5298             'blue' => [$params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}],
5299             'alpha' => (exists($params->{'gradient'}->{'start'}->{'alpha'})) ? [$params->{'gradient'}->{'start'}->{'alpha'},$params->{'gradient'}->{'end'}->{'alpha'}] : [$self->{'COLOR_ALPHA'},$self->{'COLOR_ALPHA'}],
5300             },
5301 0 0       0 $params->{'gradient'}->{'direction'}
5302             );
5303             }
5304             } elsif (exists($params->{'texture'})) {
5305 0         0 $pattern = $self->_generate_fill($width, $height, undef, $params->{'texture'});
5306             # } elsif (exists($params->{'hatch'})) {
5307             # $pattern = $self->_generate_fill($width, $height, undef, $params->{'hatch'});
5308             }
5309 0         0 my $saved = { 'x' => $left, 'y' => $top, 'width' => $width, 'height' => $height };
5310 0         0 my $saved_mode = $self->{'DRAW_MODE'};
5311 0 0       0 unless ($self->{'DRAW_MODE'}) {
5312 0 0       0 if ($self->{'ACCELERATED'}) {
5313 0         0 $self->{'DRAW_MODE'} = MASK_MODE;
5314             } else {
5315 0         0 $saved = $self->blit_read($saved);
5316 0 0       0 $saved->{'image'} = $self->_convert_16_to_24($saved->{'image'}, RGB) if ($self->{'BITS'} == 16);
5317             }
5318             }
5319 0         0 my $img;
5320             my $pimg;
5321 0         0 eval {
5322 0         0 $img = Imager->new(
5323             'xsize' => $width,
5324             'ysize' => $height,
5325             'raw_datachannels' => max(3, $bytes),
5326             'raw_storechannels' => max(3, $bytes),
5327             'channels' => max(3, $bytes),
5328             );
5329 0 0 0     0 if (exists($saved->{'image'}) && defined($saved->{'image'})) {
5330             $img->read(
5331             'xsize' => $width,
5332             'ysize' => $height,
5333             'raw_datachannels' => max(3, $bytes),
5334             'raw_storechannels' => max(3, $bytes),
5335             'channels' => max(3, $bytes),
5336             'raw_interleave' => 0,
5337 0         0 'data' => $saved->{'image'},
5338             'type' => 'raw',
5339             'allow_incomplete' => 1
5340             );
5341             }
5342 0 0 0     0 if (defined($pattern)) {
    0          
5343 0 0       0 $pattern = $self->_convert_16_to_24($pattern, RGB) if ($self->{'BITS'} == 16);
5344 0         0 $pimg = Imager->new();
5345 0         0 $pimg->read(
5346             'xsize' => $width,
5347             'ysize' => $height,
5348             'raw_datachannels' => max(3, $bytes),
5349             'raw_storechannels' => max(3, $bytes),
5350             'raw_interleave' => 0,
5351             'channels' => max(3, $bytes),
5352             'data' => $pattern,
5353             'type' => 'raw',
5354             'allow_incomplete' => 1
5355             );
5356 0         0 $fill = Imager::Fill->new('image' => $pimg);
5357             } elsif (exists($params->{'hatch'}) && defined($params->{'hatch'})) {
5358             $fill = Imager::Fill->new(
5359             'hatch' => $params->{'hatch'} || 'dots16',
5360             'fg' => $self->{'IMAGER_FOREGROUND_COLOR'},
5361 0   0     0 'bg' => $self->{'IMAGER_BACKGROUND_COLOR'}
5362             );
5363             } else {
5364 0         0 $fill = Imager::Fill->new('solid' => $self->{'IMAGER_FOREGROUND_COLOR'});
5365             }
5366             $img->polygon(
5367             'points' => $points,
5368             'color' => $self->{'IMAGER_FOREGROUND_COLOR'},
5369 0   0     0 'aa' => $params->{'antialiased'} || 0,
5370             'filled' => TRUE,
5371             'fill' => $fill,
5372             );
5373             $img->write(
5374             'type' => 'raw',
5375             'datachannels' => max(3, $bytes),
5376             'storechannels' => max(3, $bytes),
5377             'interleave' => 0,
5378 0         0 'data' => \$saved->{'image'},
5379             );
5380 0 0       0 $saved->{'image'} = $self->_convert_24_to_16($saved->{'image'}, RGB) if ($self->{'BITS'} == 16);
5381             };
5382 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
5383 0         0 $self->blit_write($saved);
5384 0         0 $self->{'DRAW_MODE'} = $saved_mode;
5385             }
5386              
5387             sub _generate_fill {
5388 0     0   0 my $self = shift;
5389 0         0 my $width = shift;
5390 0         0 my $height = shift;
5391 0         0 my $colors = shift;
5392 0         0 my $type = shift;
5393              
5394 0         0 my $gradient = '';
5395 0         0 my $bytes = $self->{'BYTES'};
5396 0 0       0 if (ref($type) eq 'HASH') { # texture
    0          
5397 0 0 0     0 if ($type->{'width'} != $width || $type->{'height'} != $height) {
5398 0         0 my $new = $self->blit_transform(
5399             {
5400             'blit_data' => $type,
5401             'scale' => {
5402             'scale_type' => 'nonprop',
5403             'x' => 0,
5404             'y' => 0,
5405             'width' => $width,
5406             'height' => $height
5407             }
5408             }
5409             );
5410 0         0 $gradient = $new->{'image'};
5411             } else {
5412 0         0 $gradient = $type->{'image'};
5413             }
5414             } elsif ($type =~ /vertical|horizontal/i) {
5415 0         0 my $r_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'};
5416 0         0 my $g_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'};
5417 0         0 my $b_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'};
5418 0         0 my $a_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'};
5419              
5420 0         0 my $r_length = $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'};
5421 0         0 my $g_length = $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'};
5422 0         0 my $b_length = $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'};
5423 0         0 my $a_length = $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'length'};
5424              
5425 0 0       0 my $count = ($type =~ /horizontal/i) ? $width : $height;
5426 0         0 my (@red,@green,@blue,@alpha);
5427 0         0 @red = @{ $colors->{'red'} };
  0         0  
5428 0         0 @green = @{ $colors->{'green'} };
  0         0  
5429 0         0 @blue = @{ $colors->{'blue'} };
  0         0  
5430 0 0       0 if ($self->{'BITS'} == 32) {
5431 0 0       0 unless (exists($colors->{'alpha'})) {
5432 0         0 @alpha = map {$_ = $self->{'COLOR_ALPHA'}} (1..$count);
  0         0  
5433             } else {
5434 0         0 @alpha = @{ $colors->{'alpha'} };
  0         0  
5435             }
5436             }
5437 0         0 my @rc = multi_gradient($count, @red);
5438 0         0 my @gc = multi_gradient($count, @green);
5439 0         0 my @bc = multi_gradient($count, @blue);
5440 0 0       0 my @ac = multi_gradient($count, @alpha) if ($self->{'BITS'} == 32);
5441 0 0       0 if ($type =~ /horizontal/i) { # Gradient
    0          
5442 0         0 my $end = $width - 1;
5443 0         0 foreach my $gcc (0 .. $end) {
5444 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
5445 0         0 $gradient .= pack('L', (
5446             ($rc[$gcc] << $r_offset) |
5447             ($gc[$gcc] << $g_offset) |
5448             ($bc[$gcc] << $b_offset) |
5449             ($ac[$gcc] << $a_offset)
5450             ));
5451             } elsif ($self->{'BITS'} == 24) {
5452 0         0 $gradient .= pack('L', (
5453             ($rc[$gcc] << $r_offset) |
5454             ($gc[$gcc] << $g_offset) |
5455             ($bc[$gcc] << $b_offset)
5456             ));
5457             } elsif ($self->{'BITS'} == 16) {
5458 0         0 $gradient .= pack('S', (
5459             (($rc[$gcc] >> 3) << $r_offset) |
5460             (($gc[$gcc] >> 2) << $g_offset) |
5461             (($bc[$gcc] >> 3) << $b_offset)
5462             ));
5463             }
5464             }
5465 0         0 $gradient = $gradient x $height;
5466             } elsif ($type =~ /vertical/i) { # gradient
5467 0         0 my $end = $height - 1;
5468 0         0 foreach my $gcc (0 .. $end) {
5469 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
5470 0         0 $gradient .= pack('L', (
5471             ($rc[$gcc] << $r_offset) |
5472             ($gc[$gcc] << $g_offset) |
5473             ($bc[$gcc] << $b_offset) |
5474             ($ac[$gcc] << $a_offset)
5475             )) x $width;
5476             } elsif ($self->{'BITS'} == 24) {
5477 0         0 $gradient .= pack('L', (
5478             ($rc[$gcc] << $r_offset) |
5479             ($gc[$gcc] << $g_offset) |
5480             ($bc[$gcc] << $b_offset)
5481             )) x $width;
5482             } elsif ($self->{'BITS'} == 16) {
5483 0         0 $gradient .= pack('S', (
5484             (($rc[$gcc] >> 3) << $r_offset) |
5485             (($gc[$gcc] >> 2) << $g_offset) |
5486             (($bc[$gcc] >> 3) << $b_offset)
5487             )) x $width;
5488             }
5489             }
5490             }
5491             } else {
5492 0 0 0     0 if ($width && $height) {
5493 0         0 my $img;
5494 0         0 eval {
5495 0         0 $img = Imager->new(
5496             'xsize' => $width,
5497             'ysize' => $height,
5498             'channels' => max(3, $bytes)
5499             );
5500              
5501             # Hatch types:
5502             #
5503             # Checkerboards -> check1x1, check2x2, check4x4
5504             # Vertical Lines -> vline1, vline2, vline4
5505             # Horizontal Lines -> hline1, hline2, hline4
5506             # 45 deg Lines -> slash1, slash2
5507             # -45 deg Lines -> slosh1, slosh2
5508             # Vertical & Horizontal Lines -> grid1, grid2, grid4
5509             # Dots -> dots1, dots4, dots16
5510             # Stipples -> stipple, stipple2
5511             # Weave -> weave
5512             # Crosshatch -> cross1, cross2
5513             # Lozenge Tiles -> vlozenge, hlozenge
5514             # Scales -> scalesdown, scalesup, scalesleft, scalesright
5515             # L Shaped Tiles -> tile_L
5516              
5517             my $fill = Imager::Fill->new(
5518             'hatch' => $type || 'dots16',
5519             'fg' => $self->{'IMAGER_FOREGROUND_COLOR'},
5520 0   0     0 'bg' => $self->{'IMAGER_BACKGROUND_COLOR'}
5521             );
5522 0         0 $img->box('fill' => $fill);
5523 0         0 $img->write(
5524             'type' => 'raw',
5525             'datachannels' => max(3, $bytes),
5526             'storechannels' => max(3, $bytes),
5527             'interleave' => 0,
5528             'data' => \$gradient
5529             );
5530             };
5531 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
5532 0 0       0 $gradient = $self->_convert_24_to_16($gradient, RGB) if ($self->{'BITS'} == 16);
5533             }
5534             }
5535 0         0 return ($gradient);
5536             }
5537              
5538             sub box {
5539             =head2 box
5540              
5541             Draws a box from point x,y to point xx,yy, either as an outline, if 'filled' is 0, or as a filled block, if 'filled' is 1. You may also add a gradient or texture.
5542              
5543             =over 4
5544              
5545             $fb->box({
5546             'x' => 20,
5547             'y' => 50,
5548             'xx' => 70,
5549             'yy' => 100,
5550             'radius' => 0, # if rounded, optional
5551             'pixel_size' => 1, # optional
5552             'filled' => 1, # optional
5553              
5554             ## Only one of the following, "filled" must be set
5555              
5556             'gradient' => { # optional
5557             'direction' => 'horizontal', # or vertical
5558             'colors' => { # 2 to any number of transitions allowed, and all colors must have the same number of transitions
5559             'red' => [255,255,0], # Red to yellow to cyan
5560             'green' => [0,255,255],
5561             'blue' => [0,0,255],
5562             'alpha' => [255,255,255], # Yes, even alpha transparency can vary
5563             }
5564             },
5565             'texture' => { # Same as what blit_read or load_image returns
5566             'width' => 320,
5567             'height' => 240,
5568             'image' => $raw_image_data
5569             },
5570             'hatch' => 'hatchname' # The exported array @HATCHES contains
5571             # the names of all the hatches
5572             });
5573              
5574             =back
5575              
5576             =cut
5577              
5578 0     0 0 0 my $self = shift;
5579 0         0 my $params = shift;
5580              
5581 0         0 my $x = int($params->{'x'});
5582 0         0 my $y = int($params->{'y'});
5583 0         0 my $xx = int($params->{'xx'});
5584 0         0 my $yy = int($params->{'yy'});
5585 0   0     0 my $filled = int($params->{'filled'}) || 0;
5586 0   0     0 my $size = int($params->{'pixel_size'}) || 1;
5587 0   0     0 my $radius = int($params->{'radius'}) || 0;
5588 0 0       0 $size = 1 if ($filled);
5589 0         0 my ($count, $data, $w, $h);
5590              
5591             # This puts $x,$y,$xx,$yy in their correct order if backwards.
5592             # $x must always be less than $xx
5593             # $y must always be less than $yy
5594 0 0       0 if ($x > $xx) {
5595 0         0 ($x, $xx) = ($xx, $x);
5596             }
5597 0 0       0 if ($y > $yy) {
5598 0         0 ($y, $yy) = ($yy, $y);
5599             }
5600 0         0 my $width = $xx - $y;
5601 0         0 my $height = $yy - $y;
5602 0         0 my $vc = $height / 2;
5603 0         0 my $hc = $width / 2;
5604 0 0       0 if ($radius) {
    0          
5605             # Keep the radius sane
5606 0 0       0 $radius = $hc if ($hc < $radius);
5607 0 0       0 $radius = $vc if ($vc < $radius);
5608              
5609 0         0 my $p = $params;
5610 0         0 $p->{'radius'} = $radius;
5611 0         0 $p->{'x'} = ($x + $radius);
5612 0         0 $p->{'y'} = ($y + $radius);
5613 0         0 $p->{'xx'} = ($xx - $radius);
5614 0         0 $p->{'yy'} = ($yy - $radius);
5615 0         0 $p->{'bx'} = $x;
5616 0         0 $p->{'by'} = $y;
5617 0         0 $p->{'bxx'} = $xx;
5618 0         0 $p->{'byy'} = $yy;
5619 0         0 $self->circle($p); # Yep, circle
5620             } elsif ($filled) {
5621 0         0 my $X = $xx;
5622 0         0 my $Y = $yy;
5623 0         0 $x = max($self->{'X_CLIP'}, min($self->{'XX_CLIP'}, $x));
5624 0         0 $y = max($self->{'Y_CLIP'}, min($self->{'YY_CLIP'}, $y));
5625 0         0 $xx = max($self->{'X_CLIP'}, min($self->{'XX_CLIP'}, $xx));
5626 0         0 $yy = max($self->{'Y_CLIP'}, min($self->{'YY_CLIP'}, $yy));
5627 0         0 $w = abs($xx - $x);
5628 0         0 $h = abs($yy - $y);
5629 0         0 my $pattern;
5630              
5631 0 0       0 if (exists($params->{'gradient'})) {
    0          
    0          
5632 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
5633 0         0 $pattern = $self->_generate_fill($w, $h, $params->{'gradient'}->{'colors'}, $params->{'gradient'}->{'direction'});
5634             } else {
5635             $pattern = $self->_generate_fill(
5636             $w, $h,
5637             {
5638             'red' => [$params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}],
5639             'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
5640             'blue' => [$params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}],
5641             'alpha' => (exists($params->{'gradient'}->{'start'}->{'alpha'})) ? [$params->{'gradient'}->{'start'}->{'alpha'},$params->{'gradient'}->{'end'}->{'alpha'}] : [$self->{'COLOR_ALPHA'},$self->{'COLOR_ALPHA'}],
5642             },
5643 0 0       0 $params->{'gradient'}->{'direction'},
5644             );
5645             }
5646             } elsif (exists($params->{'texture'})) {
5647 0         0 $pattern = $self->_generate_fill($w, $h, undef, $params->{'texture'});
5648             } elsif (exists($params->{'hatch'})) {
5649 0         0 $pattern = $self->_generate_fill($w, $h, undef, $params->{'hatch'});
5650             } else {
5651 0         0 $pattern = $self->{'RAW_FOREGROUND_COLOR'} x ($w * $h);
5652             }
5653 0         0 $self->blit_write({ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h, 'image' => $pattern });
5654 0         0 $self->{'X'} = $X;
5655 0         0 $self->{'Y'} = $Y;
5656             } else {
5657 0         0 $self->polygon({ 'coordinates' => [$x, $y, $xx, $y, $xx, $yy, $x, $yy], 'pixel_size' => $size });
5658             }
5659             }
5660              
5661             sub rbox {
5662             =head2 rbox
5663              
5664             Draws a box at point x,y with the width 'width' and height 'height'. It draws a frame if 'filled' is 0 or a filled box if 'filled' is 1. 'pixel_size' only applies if 'filled' is 0. Filled boxes draw faster than frames. Gradients or textures are also allowed.
5665              
5666             =over 4
5667              
5668             $fb->rbox({
5669             'x' => 100,
5670             'y' => 100,
5671             'width' => 200,
5672             'height' => 150,
5673             'radius' => 0, # if rounded, optional
5674             'pixel_size' => 2, # optional
5675             'filled' => 0, # optional
5676              
5677             ## Only one of the following, "filled" must be set
5678              
5679             'gradient' => { # optional
5680             'direction' => 'horizontal', # or vertical
5681             'colors' => { # 2 to any number of transitions allowed
5682             'red' => [255,255,0], # Red to yellow to cyan
5683             'green' => [0,255,255],
5684             'blue' => [0,0,255],
5685             'alpha' => [255,255,255],
5686             }
5687             },
5688             'texture' => { # Same as what blit_read or load_image returns
5689             'width' => 320,
5690             'height' => 240,
5691             'image' => $raw_image_data
5692             },
5693             'hatch' => 'hatchname' # The exported array @HATCHES contains
5694             # the names of all the hatches
5695             });
5696              
5697             =back
5698              
5699             =cut
5700              
5701 0     0 0 0 my $self = shift;
5702 0         0 my $params = shift;
5703              
5704 0         0 $params->{'xx'} = $params->{'x'} + $params->{'width'};
5705 0         0 $params->{'yy'} = $params->{'y'} + $params->{'height'};
5706 0         0 $self->box($params);
5707             }
5708              
5709             sub set_color {
5710             =head2 set_color
5711              
5712             Sets the drawing color in red, green, and blue, absolute 8 bit values.
5713              
5714             Even if you are in 16 bit color mode, use 8 bit values. They will be automatically scaled.
5715              
5716             =over 4
5717              
5718             $fb->set_color({
5719             'red' => 255,
5720             'green' => 255,
5721             'blue' => 0,
5722             'alpha' => 255
5723             });
5724              
5725             =back
5726             =cut
5727              
5728 4     4 0 18 my $self = shift;
5729 4         6 my $params = shift;
5730 4   100     59 my $name = shift || 'RAW_FOREGROUND_COLOR';
5731              
5732 4         14 my $bytes = $self->{'BYTES'};
5733 4         23 my $R = int($params->{'red'}) & 255; # Color forced to fit within 0-255 value
5734 4         11 my $G = int($params->{'green'}) & 255;
5735 4         8 my $B = int($params->{'blue'}) & 255;
5736 4 100       39 my $def_alpha = ($name eq 'RAW_FOREGROUND_COLOR') ? 255 : 0;
5737 4   66     45 my $A = int($params->{'alpha'} || $def_alpha) & 255;
5738 4         24 my $color_order = $self->{'COLOR_ORDER'};
5739              
5740 4         6 map { $self->{ $name . '_' . uc($_) } = $params->{$_} } (keys %{$params});
  16         144  
  4         58  
5741 4         19 $params->{'red'} = $R;
5742 4         13 $params->{'green'} = $G;
5743 4         15 $params->{'blue'} = $B;
5744 4         13 $params->{'alpha'} = $A;
5745 4         13 my $r_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'};
5746 4         23 my $g_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'};
5747 4         12 my $b_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'};
5748 4         12 my $a_offset = $self->{'vscreeninfo'}->{'bitfields'}->{'alpha'}->{'offset'};
5749 4         20 $self->{'COLOR_ALPHA'} = $A;
5750 4 50       23 if ($self->{'BITS'} >= 24) {
    0          
5751 4         33 $self->{$name} = pack('L',(
5752             ($R << $r_offset) |
5753             ($G << $g_offset) |
5754             ($B << $b_offset) |
5755             ($A << $a_offset)
5756             ));
5757 4 50       13 $self->{$name} = substr($self->{$name},0,3) if ($self->{'BITS'} == 24);
5758 4         39 $self->{"INT_$name"} = unpack('L', $self->{$name});
5759             } elsif ($self->{'BITS'} == 16) {
5760 0         0 my $r = $R >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'});
5761 0         0 my $g = $G >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'});
5762 0         0 my $b = $B >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});
5763 0         0 $self->{$name} = pack('S', ($r << $r_offset) | ($g << $g_offset) | ($b << $b_offset));
5764              
5765 0         0 $self->{"INT_$name"} = unpack('S', $self->{$name});
5766             }
5767              
5768 4         39 $self->{"SET_$name"} = $params;
5769             # This swapping is only for Imager
5770 4 50       50 if ($color_order == BGR) {
    50          
    50          
    50          
    50          
5771 0         0 ($B, $G, $R) = ($R,$G,$B);
5772             } elsif ($color_order == BRG) {
5773 0         0 ($B, $R, $G) = ($R,$G,$B);
5774             } elsif ($color_order == RBG) {
5775 0         0 ($R, $B, $G) = ($R,$G,$B);
5776             } elsif ($color_order == GRB) {
5777 0         0 ($G, $R, $B) = ($R,$G,$B);
5778             } elsif ($color_order == GBR) {
5779 0         0 ($G, $B, $R) = ($R,$G,$B);
5780             }
5781 4 100       30 if ($name eq 'RAW_FOREGROUND_COLOR') {
5782 2 50       127 $self->{'IMAGER_FOREGROUND_COLOR'} = ($self->{'BITS'} == 32) ? Imager::Color->new($R, $G, $B, $A) : Imager::Color->new($R, $G, $B);
5783             } else {
5784 2 50       25 $self->{'IMAGER_BACKGROUND_COLOR'} = ($self->{'BITS'} == 32) ? Imager::Color->new($R, $G, $B, $A) : Imager::Color->new($R, $G, $B);
5785             }
5786             }
5787              
5788             sub set_foreground_color {
5789             =head2 set_foreground_color
5790              
5791             This is an alias to 'set_color'
5792              
5793             =cut
5794              
5795 0     0 0 0 my $self = shift;
5796 0         0 $self->set_color(shift);
5797             }
5798              
5799             sub set_b_color {
5800             =head2 set_b_color
5801              
5802             Sets the background color in red, green, and blue values.
5803              
5804             The same rules as set_color apply.
5805              
5806             =over 4
5807              
5808             $fb->set_b_color({
5809             'red' => 0,
5810             'green' => 0,
5811             'blue' => 255,
5812             'alpha' => 255
5813             });
5814              
5815             =back
5816             =cut
5817              
5818 2     2 0 6 my $self = shift;
5819 2         67 $self->set_color(shift, 'RAW_BACKGROUND_COLOR');
5820             }
5821              
5822             sub set_background_color {
5823             =head2 set_background_color
5824              
5825             This is an alias to 'set_b_color'
5826              
5827             =cut
5828              
5829 0     0 0 0 my $self = shift;
5830 0         0 $self->set_color(shift, 'RAW_BACKGROUND_COLOR');
5831             }
5832              
5833             sub fill {
5834             =head2 fill
5835              
5836             Does a flood fill starting at point x,y. It samples the color at that point and determines that color to be the "background" color, and proceeds to fill in, with the current foreground color, until the "background" color is replaced with the new color.
5837              
5838             NOTE: The accelerated version of this routine may (and it is a small may) have issues. If you find any issues, then temporarily turn off C-acceleration when calling this method.
5839              
5840             =over 4
5841              
5842             $fb->fill({'x' => 334, 'y' => 23});
5843              
5844             =back
5845              
5846             * This one is greatly affected by the acceleration setting, and likely the one that may give the most trouble. I have found on some systems Imager just doesn't do what it is asked to, but on others it works fine. Go figure. Some if you are getting your entire screen filled and know you are placing the X,Y coordinate correctly, then disabling acceleration before calling this should fix it. Don't forget to re-enable acceleration when done.
5847              
5848             =cut
5849              
5850 0     0 0 0 my $self = shift;
5851 0         0 my $params = shift;
5852              
5853 0         0 my $x = int($params->{'x'});
5854 0         0 my $y = int($params->{'y'});
5855              
5856 0         0 my $pixel = $self->pixel({ 'x' => $x, 'y' => $y });
5857 0         0 my $bytes = $self->{'BYTES'};
5858              
5859 0 0       0 return if ($back eq $self->{'RAW_FOREGROUND_COLOR'});
5860 0 0       0 unless ($self->{'ACCELERATED'}) {
5861 0         0 my $background = $pixel->{'raw'};
5862 0         0 my %visited = ();
5863 0         0 my @queue = ();
5864              
5865 0         0 push(@queue, [$x, $y]);
5866              
5867 0         0 while (scalar(@queue)) {
5868 0         0 my $pointref = shift(@queue);
5869 0         0 ($x, $y) = @{$pointref};
  0         0  
5870 0 0 0     0 next if (($x < $self->{'X_CLIP'}) || ($x > $self->{'XX_CLIP'}) || ($y < $self->{'Y_CLIP'}) || ($y > $self->{'YY_CLIP'}));
      0        
      0        
5871 0 0       0 unless (exists($visited{"$x,$y"})) {
5872 0         0 $pixel = $self->pixel({ 'x' => $x, 'y' => $y, 'raw' => TRUE });
5873 0 0       0 if ($pixel eq $background) {
5874 0         0 $self->plot({ 'x' => $x, 'y' => $y });
5875 0         0 $visited{"$x,$y"}++;
5876 0         0 push(@queue, [$x + 1, $y]);
5877 0         0 push(@queue, [$x - 1, $y]);
5878 0         0 push(@queue, [$x, $y + 1]);
5879 0         0 push(@queue, [$x, $y - 1]);
5880             }
5881             }
5882             }
5883             } else {
5884 0         0 my $width = int($self->{'W_CLIP'});
5885 0         0 my $height = int($self->{'H_CLIP'});
5886 0         0 my $pattern;
5887 0 0       0 if (exists($params->{'gradient'})) {
    0          
    0          
5888 0   0     0 $params->{'gradient'}->{'direction'} ||= 'vertical';
5889 0 0       0 if (exists($params->{'gradient'}->{'colors'})) {
5890 0         0 $pattern = $self->_generate_fill($width, $height, $params->{'gradient'}->{'colors'}, $params->{'gradient'}->{'direction'});
5891             } else {
5892             $pattern = $self->_generate_fill(
5893             $width, $height,
5894             {
5895             'red' => [$params->{'gradient'}->{'start'}->{'red'}, $params->{'gradient'}->{'end'}->{'red'}],
5896             'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
5897             'blue' => [$params->{'gradient'}->{'start'}->{'blue'}, $params->{'gradient'}->{'end'}->{'blue'}],
5898             'alpha' => (exists($params->{'gradient'}->{'start'}->{'alpha'})) ? [$params->{'gradient'}->{'start'}->{'alpha'},$params->{'gradient'}->{'end'}->{'alpha'}] : [$self->{'COLOR_ALPHA'},$self->{'COLOR_ALPHA'}],
5899             },
5900 0 0       0 $params->{'gradient'}->{'direction'}
5901             );
5902             }
5903             } elsif (exists($params->{'texture'})) {
5904 0         0 $pattern = $self->_generate_fill($width, $height, undef, $params->{'texture'});
5905             } elsif (exists($params->{'hatch'})) {
5906 0         0 $pattern = $self->_generate_fill($width, $height, undef, $params->{'hatch'});
5907             }
5908              
5909             my $saved = $self->blit_read(
5910             {
5911             'x' => $self->{'X_CLIP'},
5912 0         0 'y' => $self->{'Y_CLIP'},
5913             'width' => $width,
5914             'height' => $height,
5915             }
5916             );
5917 0 0       0 if ($self->{'BITS'} == 16) {
5918 0         0 $saved->{'image'} = $self->_convert_16_to_24($saved->{'image'}, RGB);
5919 0 0       0 $pattern = $self->_convert_16_to_24($pattern, RGB) if (defined($pattern));
5920             }
5921 0         0 eval {
5922 0         0 my $img = Imager->new(
5923             'xsize' => $width,
5924             'ysize' => $height,
5925             'raw_datachannels' => max(3, $bytes),
5926             'raw_storechannels' => max(3, $bytes),
5927             'channels' => max(3, $bytes),
5928             );
5929              
5930             # unless ($self->{'DRAW_MODE'}) {
5931             $img->read(
5932             'xsize' => $width,
5933             'ysize' => $height,
5934             'raw_datachannels' => max(3, $bytes),
5935             'raw_storechannels' => max(3, $bytes),
5936             'channels' => max(3, $bytes),
5937             'raw_interleave' => 0,
5938 0         0 'data' => $saved->{'image'},
5939             'type' => 'raw',
5940             'allow_incomplete' => 1
5941             );
5942              
5943 0         0 my $fill;
5944 0 0       0 if (defined($pattern)) {
5945 0         0 my $pimg = Imager->new();
5946 0         0 $pimg->read(
5947             'xsize' => $width,
5948             'ysize' => $height,
5949             'raw_datachannels' => max(3, $bytes),
5950             'raw_storechannels' => max(3, $bytes),
5951             'raw_interleave' => 0,
5952             'channels' => max(3, $bytes),
5953             'data' => $pattern,
5954             'type' => 'raw',
5955             'allow_incomplete' => 1
5956             );
5957             $img->flood_fill(
5958             'x' => int($x - $self->{'X_CLIP'}),
5959             'y' => int($y - $self->{'Y_CLIP'}),
5960 0         0 'color' => $self->{'IMAGER_FOREGROUND_COLOR'},
5961             'fill' => { 'image' => $pimg }
5962             );
5963             } else {
5964             $img->flood_fill(
5965             'x' => int($x - $self->{'X_CLIP'}),
5966             'y' => int($y - $self->{'Y_CLIP'}),
5967 0         0 'color' => $self->{'IMAGER_FOREGROUND_COLOR'},
5968             );
5969             }
5970             $img->write(
5971             'type' => 'raw',
5972             'datachannels' => max(3, $bytes),
5973             'storechannels' => max(3, $bytes),
5974             'interleave' => 0,
5975 0         0 'data' => \$saved->{'image'},
5976             );
5977 0 0       0 $saved->{'image'} = $self->_convert_24_to_16($saved->{'image'}, RGB) if ($self->{'BITS'} == 16);
5978             };
5979 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
5980              
5981 0         0 $self->blit_write($saved);
5982             }
5983             }
5984              
5985             sub replace_color {
5986             =head2 replace_color
5987              
5988             This replaces one color with another inside the clipping region. Sort of like a fill without boundary checking.
5989              
5990             In 32 bit mode, the replaced alpha channel is ALWAYS set to 255.
5991              
5992             =over 4
5993              
5994             $fb->replace_color({
5995             'old' => { # Changed as of 5.56
5996             'red' => 23,
5997             'green' => 48,
5998             'blue' => 98
5999             },
6000             'new' => {
6001             'red' => 255,
6002             'green' => 255,
6003             'blue' => 0
6004             }
6005             });
6006              
6007             =back
6008              
6009             * This is not affected by the Acceleration setting, and is just as fast in 16 bit as it is in 24 and 32 bit modes. Which means, very fast.
6010              
6011             =cut
6012              
6013 0     0 0 0 my $self = shift;
6014 0         0 my $params = shift;
6015              
6016 0   0     0 my $old_r = int($params->{'old'}->{'red'}) || 0;
6017 0   0     0 my $old_g = int($params->{'old'}->{'green'}) || 0;
6018 0   0     0 my $old_b = int($params->{'old'}->{'blue'}) || 0;
6019 0 0       0 my $old_a = int($params->{'old'}->{'alpha'}) if (exists($params->{'old'}->{'alpha'}));
6020 0   0     0 my $new_r = int($params->{'new'}->{'red'}) || 0;
6021 0   0     0 my $new_g = int($params->{'new'}->{'green'}) || 0;
6022 0   0     0 my $new_b = int($params->{'new'}->{'blue'}) || 0;
6023 0   0     0 my $new_a = int($params->{'new'}->{'alpha'}) || $self->{'COLOR_ALPHA'};
6024              
6025 0         0 my $color_order = $self->{'COLOR_ORDER'};
6026 0         0 my ($sx, $start) = (0, 0);
6027 0         0 $self->set_color({ 'red' => $new_r, 'green' => $new_g, 'blue' => $new_b });
6028 0         0 my $old_mode = $self->{'DRAW_MODE'};
6029 0         0 $self->{'DRAW_MODE'} = NORMAL_MODE;
6030              
6031 0         0 my ($old, $new);
6032 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
6033 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
6034 0 0       0 $old = (defined($old_a)) ? sprintf('\x%02x\x%02x\x%02x\x%02x', $old_b, $old_g, $old_r, $old_a) : sprintf('\x%02x\x%02x\x%02x.', $old_b, $old_g, $old_r);
6035 0         0 $new = sprintf('\x%02x\x%02x\x%02x\x%02x', $new_b, $new_g, $new_r, $new_a);
6036             } elsif ($color_order == BRG) {
6037 0 0       0 $old = (defined($old_a)) ? sprintf('\x%02x\x%02x\x%02x\x%02x', $old_b, $old_r, $old_g, $old_a) : sprintf('\x%02x\x%02x\x%02x.', $old_b, $old_r, $old_g);
6038 0         0 $new = sprintf('\x%02x\x%02x\x%02x\x%02x', $new_b, $new_r, $new_g, $new_a);
6039             } elsif ($color_order == RGB) {
6040 0 0       0 $old = (defined($old_a)) ? sprintf('\x%02x\x%02x\x%02x\x%02x', $old_r, $old_g, $old_b, $old_a) : sprintf('\x%02x\x%02x\x%02x.', $old_r, $old_g, $old_b);
6041 0         0 $new = sprintf('\x%02x\x%02x\x%02x\x%02x', $new_r, $new_g, $new_b, $new_a);
6042             } elsif ($color_order == RBG) {
6043 0 0       0 $old = (defined($old_a)) ? sprintf('\x%02x\x%02x\x%02x\x%02x', $old_r, $old_b, $old_g, $old_a) : sprintf('\x%02x\x%02x\x%02x.', $old_r, $old_b, $old_g);
6044 0         0 $new = sprintf('\x%02x\x%02x\x%02x\x%02x', $new_r, $new_b, $new_g, $new_a);
6045             } elsif ($color_order == GRB) {
6046 0 0       0 $old = (defined($old_a)) ? sprintf('\x%02x\x%02x\x%02x\x%02x', $old_g, $old_r, $old_b, $old_a) : sprintf('\x%02x\x%02x\x%02x.', $old_g, $old_r, $old_b);
6047 0         0 $new = sprintf('\x%02x\x%02x\x%02x\x%02x', $new_g, $new_r, $new_b, $new_a);
6048             } elsif ($color_order == GBR) {
6049 0 0       0 $old = (defined($old_a)) ? sprintf('\x%02x\x%02x\x%02x\x%02x', $old_g, $old_b, $old_r, $old_a) : sprintf('\x%02x\x%02x\x%02x.', $old_g, $old_b, $old_r);
6050 0         0 $new = sprintf('\x%02x\x%02x\x%02x\x%02x', $new_g, $new_b, $new_r, $new_a);
6051             }
6052             } elsif ($self->{'BITS'} == 24) {
6053 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
6054 0         0 $old = sprintf('\x%02x\x%02x\x%02x', $old_b, $old_g, $old_r);
6055 0         0 $new = sprintf('\x%02x\x%02x\x%02x', $new_b, $new_g, $new_r);
6056             } elsif ($color_order == BRG) {
6057 0         0 $old = sprintf('\x%02x\x%02x\x%02x', $old_b, $old_r, $old_g);
6058 0         0 $new = sprintf('\x%02x\x%02x\x%02x', $new_b, $new_r, $new_g);
6059             } elsif ($color_order == RGB) {
6060 0         0 $old = sprintf('\x%02x\x%02x\x%02x.', $old_r, $old_g, $old_b);
6061 0         0 $new = sprintf('\x%02x\x%02x\x%02x', $new_r, $new_g, $new_b);
6062             } elsif ($color_order == RBG) {
6063 0         0 $old = sprintf('\x%02x\x%02x\x%02x.', $old_r, $old_b, $old_g);
6064 0         0 $new = sprintf('\x%02x\x%02x\x%02x', $new_r, $new_b, $new_g);
6065             } elsif ($color_order == GRB) {
6066 0         0 $old = sprintf('\x%02x\x%02x\x%02x.', $old_g, $old_r, $old_b);
6067 0         0 $new = sprintf('\x%02x\x%02x\x%02x', $new_g, $new_r, $new_b);
6068             } elsif ($color_order == GBR) {
6069 0         0 $old = sprintf('\x%02x\x%02x\x%02x.', $old_g, $old_b, $old_r);
6070 0         0 $new = sprintf('\x%02x\x%02x\x%02x', $new_g, $new_b, $new_r);
6071             }
6072             } elsif ($self->{'BITS'} == 16) {
6073 0         0 $old_b = $old_b >> (8 - ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'}));
6074 0         0 $old_g = $old_g >> (8 - ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'}));
6075 0         0 $old_r = $old_r >> (8 - ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'}));
6076 0         0 $new_b = $new_b >> (8 - ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'}));
6077 0         0 $new_g = $new_g >> (8 - ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'}));
6078 0         0 $new_r = $new_r >> (8 - ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'}));
6079             $old = pack('S',
6080             (
6081             ($old_b << $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}) |
6082             ($old_g << $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}) |
6083 0         0 ($old_r << $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})
6084             )
6085             );
6086             $new = pack('S',
6087             (
6088             ($new_b << $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}) |
6089             ($new_g << $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}) |
6090 0         0 ($new_r << $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})
6091             )
6092             );
6093 0         0 $old = sprintf('\x%02x\x%02x', unpack('C2', $old));
6094 0         0 $new = sprintf('\x%02x\x%02x', unpack('C2', $new));
6095             }
6096             my $save = $self->blit_read(
6097             {
6098             'x' => $self->{'X_CLIP'},
6099             'y' => $self->{'Y_CLIP'},
6100             'width' => $self->{'W_CLIP'},
6101 0         0 'height' => $self->{'H_CLIP'}
6102             }
6103             );
6104              
6105 0         0 eval(" \$save->{'image'} =~ s/$old/$new/sg; ");
6106 0         0 $self->blit_write($save);
6107              
6108 0         0 $self->{'DRAW_MODE'} = $old_mode;
6109             }
6110              
6111             sub blit_copy {
6112             =head2 blit_copy
6113              
6114             Copies a square portion of screen graphic data from x,y,w,h to x_dest,y_dest. It copies in the current drawing mode.
6115              
6116             =over 4
6117              
6118             $fb->blit_copy({
6119             'x' => 20,
6120             'y' => 20,
6121             'width' => 30,
6122             'height' => 30,
6123             'x_dest' => 200,
6124             'y_dest' => 200
6125             });
6126              
6127             =back
6128              
6129             =cut
6130              
6131 0     0 0 0 my $self = shift;
6132 0         0 my $params = shift;
6133              
6134 0         0 $self->blit_write({ %{ $self->blit_read({ 'x' => int($params->{'x'}), 'y' => int($params->{'y'}), 'width' => int($params->{'width'}), 'height' => int($params->{'height'}) }) }, 'x' => int($params->{'x_dest'}), 'y' => int($params->{'y_dest'}) });
  0         0  
6135             }
6136              
6137             sub blit_move {
6138             =head2 blit_move
6139              
6140             Moves a square portion of screen graphic data from x,y,w,h to x_dest,y_dest. It moves in the current drawing mode. It differs from "blit_copy" in that it removes the graphic from the original location (via XOR).
6141              
6142             It also returns the data moved like "blit_read"
6143              
6144             =over 4
6145              
6146             $fb->blit_move({
6147             'x' => 20,
6148             'y' => 20,
6149             'width' => 30,
6150             'height' => 30,
6151             'x_dest' => 200,
6152             'y_dest' => 200,
6153             'image' => $raw_image_data, # This is optional, but can speed things up
6154             });
6155              
6156             =back
6157              
6158             =cut
6159              
6160 0     0 0 0 my $self = shift;
6161 0         0 my $params = shift;
6162              
6163 0         0 my $old_mode = $self->{'DRAW_MODE'};
6164             my $image = (exists($params->{'image'})) ?
6165             $params
6166             :
6167 0 0       0 $self->blit_read({ 'x' => int($params->{'x'}), 'y' => int($params->{'y'}), 'width' => int($params->{'width'}), 'height' => int($params->{'height'}) });
6168 0         0 $self->xor_mode();
6169 0         0 $self->blit_write($image);
6170 0         0 $self->{'DRAW_MODE'} = $old_mode;
6171 0         0 $image->{'x'} = int($params->{'x_dest'});
6172 0         0 $image->{'y'} = int($params->{'y_dest'});
6173 0         0 $self->vsync();
6174 0         0 $self->blit_write($image);
6175 0         0 delete($image->{'x_dest'});
6176 0         0 delete($image->{'y_dest'});
6177 0         0 return($image);
6178             }
6179              
6180             sub play_animation {
6181             =head2 play_animation
6182              
6183             Plays an animation sequence loaded from "load_image"
6184              
6185             =over 4
6186              
6187             my $animation = $fb->load_image(
6188             {
6189             'file' => 'filename.gif',
6190             'center' => CENTER_XY,
6191             }
6192             );
6193              
6194             $fb->play_animation($animation,$rate_multiplier);
6195              
6196             =back
6197              
6198             The animation is played at the speed described by the file's metadata multiplied by "rate_multiplier".
6199              
6200             You need to enclose this in a loop if you wish it to play more than once.
6201              
6202             =cut
6203              
6204 0     0 0 0 my $self = shift;
6205 0         0 my $image = shift;
6206 0   0     0 my $rate = shift || 1;
6207              
6208 0         0 foreach my $frame (0 .. (scalar(@{$image}) - 1)) {
  0         0  
6209 0         0 my $begin = time;
6210 0         0 $self->blit_write($image->[$frame]);
6211              
6212 0         0 my $delay = (($image->[$frame]->{'tags'}->{'gif_delay'} * .01) * $rate) - (time - $begin);
6213 0 0       0 if ($delay > 0) {
6214 0         0 sleep $delay;
6215             }
6216             }
6217             }
6218              
6219             sub acceleration {
6220             =head2 acceleration
6221              
6222             Enables/Disables all Imager or C language acceleration.
6223              
6224             GFB uses the Imager library to do some drawing. In some cases, these may not function as they should on some systems. This method allows you to toggle this acceleration on or off.
6225              
6226             When acceleration is off, the underlying (slower) Perl algorithms are used. It is advisable to leave acceleration on for those methods which it functions correctly, and only shut it off when calling the problem ones.
6227              
6228             When called without parameters, it returns the current setting.
6229              
6230             =over 4
6231              
6232             $fb->acceleration(HARDWARE); # Turn hardware acceleration ON, along with some C acceleration (HARDWARE IS NOT YET IMPLEMENTED!)
6233              
6234             $fb->acceleration(SOFTWARE); # Turn C (software) acceleration ON
6235              
6236             $fb->acceleration(PERL); # Turn acceleration OFF, using Perl
6237              
6238             my $accel = $fb->acceleration(); # Get current acceleration state. 0 = PERL, 1 = SOFTWARE, 2 = HARDWARE (not yet implemented)
6239              
6240             my $accel = $fb->acceleration('english'); # Get current acceleration state in an english string.
6241             # "PERL" = PERL = 0
6242             # "SOFTWARE" = SOFTWARE = 1
6243             # "HARDWARE" = HARDWARE = 2
6244              
6245             =back
6246              
6247             * The "Mask" and "Unmask" drawing modes are greatly affected by acceleration, as well as 16 bit conversions in image loading and ttf_print(ing).
6248              
6249             =cut
6250              
6251 0     0 0 0 my $self = shift;
6252 0 0       0 if (scalar(@_)) {
6253 0         0 my $set = shift;
6254 0 0 0     0 if ($set =~ /^\d+$/ && $set >= PERL && $set <= HARDWARE) {
    0 0        
6255 0         0 $self->{'ACCELERATED'} = $set;
6256             } elsif ($set =~ /english|string/i) {
6257 0         0 foreach my $name (qw( PERL SOFTWARE HARDWARE )) {
6258 0 0       0 if ($self->{'ACCELERATED'} == $self->{$name}) {
6259 0         0 return($name);
6260             }
6261             }
6262             }
6263             }
6264 0         0 return ($self->{'ACCELERATED'});
6265             }
6266              
6267             sub perl {
6268             =head2 perl
6269              
6270             This is an alias to "acceleration(PERL)"
6271              
6272             =cut
6273              
6274 0     0 0 0 my $self = shift;
6275 0         0 $self->acceleration(PERL);
6276             }
6277              
6278             sub software {
6279             =head2 software
6280              
6281             This is an alias to "acceleration(SOFTWARE)"
6282              
6283             =cut
6284              
6285 0     0 0 0 my $self = shift;
6286 0         0 $self->acceleration(SOFTWARE);
6287             }
6288              
6289             sub hardware {
6290             =head2 hardware
6291              
6292             This is an alias to "acceleration(HARDWARE)"
6293              
6294             =cut
6295              
6296 0     0 0 0 my $self = shift;
6297 0         0 $self->acceleration(HARDWARE);
6298             }
6299              
6300             sub blit_read {
6301             =head2 blit_read
6302              
6303             Reads in a square portion of screen data at x,y,width,height, and returns a hash reference with information about the block, including the raw data as a string, ready to be used with 'blit_write'.
6304              
6305             Passing no parameters automatically grabs the clipping region (the whole screen if clipping is off).
6306              
6307             =over 4
6308              
6309             my $blit_data = $fb->blit_read({
6310             'x' => 30,
6311             'y' => 50,
6312             'width' => 100,
6313             'height' => 100
6314             });
6315              
6316             =back
6317              
6318             Returns:
6319              
6320             =over 4
6321              
6322             {
6323             'x' => original X position,
6324             'y' => original Y position,
6325             'width' => width,
6326             'height' => height,
6327             'image' => string of image data for the block
6328             }
6329              
6330             =back
6331              
6332             All you have to do is change X and Y, and just pass it to "blit_write" and it will paste it there.
6333              
6334             =cut
6335              
6336 0     0 0 0 my $self = shift;
6337 0         0 my $params = shift; # $self->_blit_adjust_for_clipping(shift);
6338              
6339 0   0     0 my $x = int($params->{'x'} || $self->{'X_CLIP'});
6340 0   0     0 my $y = int($params->{'y'} || $self->{'Y_CLIP'});
6341 0         0 my $clipw = $self->{'W_CLIP'};
6342 0         0 my $cliph = $self->{'H_CLIP'};
6343 0   0     0 my $w = int($params->{'width'} || $clipw);
6344 0   0     0 my $h = int($params->{'height'} || $cliph);
6345 0         0 my $buf;
6346              
6347 0 0       0 $x = 0 if ($x < 0);
6348 0 0       0 $y = 0 if ($y < 0);
6349 0 0       0 $w = $self->{'XX_CLIP'} - $x if ($w > ($clipw));
6350 0 0       0 $h = $self->{'YY_CLIP'} - $y if ($h > ($cliph));
6351              
6352 0         0 my $W = $w * $self->{'BYTES'};
6353 0         0 my $scrn = '';
6354 0 0 0     0 if ($h > 1 && $self->{'ACCELERATED'} == SOFTWARE) {
6355 0         0 $scrn = chr(0) x ($W * $h);
6356             c_blit_read(
6357             $self->{'SCREEN'},
6358             $self->{'XRES'}, $self->{'YRES'},
6359             $self->{'BYTES_PER_LINE'},
6360             $self->{'XOFFSET'}, $self->{'YOFFSET'},
6361             $scrn,
6362             $x, $y, $w, $h,
6363             $self->{'BYTES'},
6364             $draw_mode,
6365             $self->{'COLOR_ALPHA'},
6366             $self->{'RAW_BACKGROUND_COLOR'},
6367 0         0 $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'}
6368             );
6369             } else {
6370 0         0 my $yend = $y + $h;
6371 0         0 my $XX = ($self->{'XOFFSET'} + $x) * $self->{'BYTES'};
6372 0         0 foreach my $line ($y .. ($yend - 1)) {
6373 0         0 my $index = ($self->{'BYTES_PER_LINE'} * ($line + $self->{'YOFFSET'})) + $XX;
6374 0         0 $scrn .= substr($self->{'SCREEN'}, $index, $W);
6375             }
6376             }
6377 0         0 return ({ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h, 'image' => $scrn });
6378             }
6379              
6380             sub blit_write {
6381             =head2 blit_write
6382              
6383             Writes a previously read block of screen data at x,y,width,height.
6384              
6385             It takes a hash reference. It draws in the current drawing mode.
6386              
6387             =over 4
6388              
6389             $fb->blit_write({
6390             'x' => 0,
6391             'y' => 0,
6392             'width' => 100,
6393             'height' => 100,
6394             'image' => $blit_data
6395             });
6396              
6397             =back
6398              
6399             =cut
6400              
6401 0     0 0 0 my $self = shift;
6402 0         0 my $pparams = shift;
6403 0 0       0 return unless(defined($pparams));
6404              
6405 0         0 my $params = $self->_blit_adjust_for_clipping($pparams);
6406 0 0       0 return unless (defined($params));
6407              
6408 0   0     0 my $x = int($params->{'x'} || 0);
6409 0   0     0 my $y = int($params->{'y'} || 0);
6410 0   0     0 my $w = int($params->{'width'} || 1);
6411 0   0     0 my $h = int($params->{'height'} || 1);
6412              
6413 0         0 my $draw_mode = $self->{'DRAW_MODE'};
6414 0         0 my $bytes = $self->{'BYTES'};
6415              
6416 0 0 0     0 return unless (defined($params->{'image'}) && $params->{'image'} ne '' && $h && $w);
      0        
      0        
6417              
6418 0 0       0 if ($self->{'ACCELERATED'} == SOFTWARE) { # && $h > 1) {
6419             c_blit_write(
6420             $self->{'SCREEN'},
6421             $self->{'XRES'}, $self->{'YRES'},
6422             $self->{'BYTES_PER_LINE'},
6423             $self->{'XOFFSET'}, $self->{'YOFFSET'},
6424             $params->{'image'},
6425             $x, $y, $w, $h,
6426             $bytes,
6427             $draw_mode,
6428             $self->{'COLOR_ALPHA'},
6429             $self->{'RAW_BACKGROUND_COLOR'},
6430 0         0 $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'}
6431             );
6432             } else {
6433 0         0 my $scrn = $params->{'image'};
6434 0         0 my $max = $self->{'fscreeninfo'}->{'smem_len'} - $bytes;
6435 0         0 my $scan = $w * $bytes;
6436 0         0 my $yend = $y + $h;
6437              
6438             # my $WW = $scan * $h;
6439 0         0 my $WW = int((length($scrn) / $h));
6440 0         0 my $X_X = ($x + $self->{'XOFFSET'}) * $bytes;
6441 0         0 my ($index, $data, $px, $line, $idx, $px4, $buf, $ipx);
6442              
6443 0         0 $idx = 0;
6444 0         0 $y += $self->{'YOFFSET'};
6445 0         0 $yend += $self->{'YOFFSET'};
6446              
6447 0         0 eval {
6448 0         0 foreach $line ($y .. ($yend - 1)) {
6449 0         0 $index = ($self->{'BYTES_PER_LINE'} * $line) + $X_X;
6450 0 0 0     0 if ($index >= 0 && $index <= $max && $idx >= 0 && $idx <= (length($scrn) - $bytes)) {
      0        
      0        
6451 0 0       0 if ($draw_mode == NORMAL_MODE) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
6452 0         0 substr($self->{'SCREEN'}, $index, $scan) = substr($scrn, $idx, $scan);
6453             } elsif ($draw_mode == XOR_MODE) {
6454 0         0 substr($self->{'SCREEN'}, $index, $scan) ^= substr($scrn, $idx, $scan);
6455             } elsif ($draw_mode == OR_MODE) {
6456 0         0 substr($self->{'SCREEN'}, $index, $scan) |= substr($scrn, $idx, $scan);
6457             } elsif ($draw_mode == ADD_MODE) {
6458 0         0 substr($self->{'SCREEN'}, $index, $scan) += substr($scrn, $idx, $scan);
6459             } elsif ($draw_mode == SUBTRACT_MODE) {
6460 0         0 substr($self->{'SCREEN'}, $index, $scan) -= substr($scrn, $idx, $scan);
6461             } elsif ($draw_mode == MULTIPLY_MODE) {
6462 0         0 substr($self->{'SCREEN'}, $index, $scan) *= substr($scrn, $idx, $scan);
6463             } elsif ($draw_mode == DIVIDE_MODE) {
6464 0         0 substr($self->{'SCREEN'}, $index, $scan) /= substr($scrn, $idx, $scan);
6465             } elsif ($draw_mode == ALPHA_MODE) {
6466 0         0 foreach $px (0 .. ($w - 1)) {
6467 0         0 $px4 = $px * $bytes;
6468 0         0 $ipx = $index + $px4;
6469 0   0     0 $data = substr($self->{'SCREEN'}, $ipx, $bytes) || chr(0) x $bytes;
6470 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
6471 0         0 my ($r, $g, $b, $a) = unpack("C$bytes", $data);
6472 0         0 my ($R, $G, $B, $A) = unpack("C$bytes", substr($scrn, ($idx + $px4), $bytes));
6473 0         0 my $invA = (255 - $A);
6474 0         0 $r = int(($R * $A) + ($r * $invA)) >> 8;
6475 0         0 $g = int(($G * $A) + ($g * $invA)) >> 8;
6476 0         0 $b = int(($B * $A) + ($b * $invA)) >> 8;
6477              
6478 0         0 my $c = pack("C$bytes", $r, $g, $b, $A);
6479 0 0       0 if (substr($scrn, ($idx + $px4), $bytes) ne $c) {
6480 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = $c;
6481             }
6482             } elsif ($self->{'BITS'} == 24) {
6483 0         0 my ($r, $g, $b) = unpack("C$bytes", $data);
6484 0         0 my ($R, $G, $B) = unpack("C$bytes", substr($scrn, ($idx + $px4), $bytes));
6485 0         0 my $A = $self->{'COLOR_ALPHA'};
6486 0         0 my $invA = (255 - $A);
6487 0         0 $r = int(($R * $A) + ($r * $invA)) >> 8;
6488 0         0 $g = int(($G * $A) + ($g * $invA)) >> 8;
6489 0         0 $b = int(($B * $A) + ($b * $invA)) >> 8;
6490 0         0 my $c = pack('C3', $r, $g, $b);
6491              
6492 0 0       0 if (substr($scrn, ($idx + $px4), $bytes) ne $c) {
6493 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = $c;
6494             }
6495             } elsif ($self->{'BITS'} == 16) {
6496 0         0 my $big = $self->RGB565_to_RGB888({ 'color' => $data });
6497 0         0 my ($r, $g, $b) = unpack('C3', $big->{'color'});
6498 0         0 $big = $self->RGB565_to_RGB888({ 'color' => substr($scrn, ($idx + $px4, $bytes)) });
6499 0         0 my ($R, $G, $B) = unpack('C3', $big->{'color'});
6500 0         0 my $A = $self->{'COLOR_ALPHA'};
6501 0         0 my $invA = (255 - $A);
6502 0         0 $r = int(($R * $A) + ($r * $invA)) >> 8;
6503 0         0 $g = int(($G * $A) + ($g * $invA)) >> 8;
6504 0         0 $b = int(($B * $A) + ($b * $invA)) >> 8;
6505 0         0 my $c = $self->RGB888_to_RGB565({ 'color' => pack('C3', $r, $g, $b) });
6506 0         0 $c = $c->{'color'};
6507              
6508 0 0       0 if (substr($scrn, ($idx + $px4), $bytes) ne $c) {
6509 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = $c;
6510             }
6511             }
6512             }
6513             } elsif ($draw_mode == AND_MODE) {
6514 0         0 substr($self->{'SCREEN'}, $index, $scan) &= substr($scrn, $idx, $scan);
6515             } elsif ($draw_mode == MASK_MODE) {
6516 0         0 foreach $px (0 .. ($w - 1)) {
6517 0         0 $px4 = $px * $bytes;
6518 0         0 $ipx = $index + $px4;
6519 0   0     0 $data = substr($self->{'SCREEN'}, $ipx, $bytes) || chr(0) x $bytes;
6520 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
6521 0 0       0 if (substr($scrn, ($idx + $px4), 3) ne substr($self->{'RAW_BACKGROUND_COLOR'}, 0, 3)) {
6522 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
6523             }
6524             } elsif ($self->{'BITS'} == 24) {
6525 0 0       0 if (substr($scrn, ($idx + $px4), 3) ne $self->{'RAW_BACKGROUND_COLOR'}) {
6526 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
6527             }
6528             } elsif ($self->{'BITS'} == 16) {
6529 0 0       0 if (substr($scrn, ($idx + $px4), 2) ne $self->{'RAW_BACKGROUND_COLOR'}) {
6530 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
6531             }
6532             }
6533             }
6534             } elsif ($draw_mode == UNMASK_MODE) {
6535 0         0 foreach $px (0 .. ($w - 1)) {
6536 0         0 $px4 = $px * $bytes;
6537 0         0 $ipx = $index + $px4;
6538 0         0 $data = substr($self->{'SCREEN'}, $ipx, $bytes);
6539 0 0       0 if ($self->{'BITS'} == 32) {
    0          
    0          
6540 0 0       0 if (substr($self->{'SCREEN'}, $ipx, 3) eq substr($self->{'RAW_BACKGROUND_COLOR'}, 0, 3)) {
6541 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
6542             }
6543             } elsif ($self->{'BITS'} == 24) {
6544 0 0       0 if (substr($self->{'SCREEN'}, $ipx, 3) eq $self->{'RAW_BACKGROUND_COLOR'}) {
6545 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
6546             }
6547             } elsif ($self->{'BITS'} == 16) {
6548 0 0       0 if (substr($self->{'SCREEN'}, $ipx, 2) eq $self->{'RAW_BACKGROUND_COLOR'}) {
6549 0         0 substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
6550             }
6551             }
6552             }
6553             }
6554 0         0 $idx += $WW;
6555             }
6556             }
6557             };
6558 0 0       0 if ($@) {
6559 0 0       0 warn __LINE__ . " $@" if ($self->{'SHOW_ERRORS'});
6560 0         0 $self->_fix_mapping();
6561             }
6562             }
6563             }
6564              
6565             sub _blit_adjust_for_clipping {
6566             # Chops up the blit image to stay within the clipping (and screen) boundaries
6567             # This prevents nasty crashes
6568 0     0   0 my $self = shift;
6569 0         0 my $pparams = shift;
6570              
6571 0         0 my $bytes = $self->{'BYTES'};
6572 0         0 my $yclip = $self->{'Y_CLIP'};
6573 0         0 my $xclip = $self->{'X_CLIP'};
6574 0         0 my $yyclip = $self->{'YY_CLIP'};
6575 0         0 my $xxclip = $self->{'XX_CLIP'};
6576 0         0 my $params;
6577              
6578             # Make a copy so the original isn't modified.
6579 0         0 %{$params} = %{$pparams};
  0         0  
  0         0  
6580              
6581             # First fix the vertical errors
6582 0         0 my $XX = $params->{'x'} + $params->{'width'};
6583 0         0 my $YY = $params->{'y'} + $params->{'height'};
6584 0 0 0     0 return (undef) if ($YY < $yclip || $params->{'height'} < 1 || $XX < $xclip || $params->{'x'} > $xxclip);
      0        
      0        
6585 0 0       0 if ($params->{'y'} < $yclip) { # Top
6586 0         0 $params->{'image'} = substr($params->{'image'}, ($yclip - $params->{'y'}) * ($params->{'width'} * $bytes));
6587 0         0 $params->{'height'} -= ($yclip - $params->{'y'});
6588 0         0 $params->{'y'} = $yclip;
6589             }
6590 0         0 $YY = $params->{'y'} + $params->{'height'};
6591 0 0       0 return (undef) if ($params->{'height'} < 1);
6592 0 0       0 if ($YY > $yyclip) { # Bottom
6593 0         0 $params->{'image'} = substr($params->{'image'}, 0, ($yyclip - $params->{'y'}) * ($params->{'width'} * $bytes));
6594 0         0 $params->{'height'} = $yyclip - $params->{'y'};
6595             }
6596              
6597             # Now we fix the horizontal errors
6598 0 0       0 if ($params->{'x'} < $xclip) { # Left
6599 0         0 my $line = $params->{'width'} * $bytes;
6600 0         0 my $index = ($xclip - $params->{'x'}) * $bytes;
6601 0         0 my $w = $params->{'width'} - ($xclip - $params->{'x'});
6602 0         0 my $new = '';
6603 0         0 foreach my $yl (0 .. ($params->{'height'} - 1)) {
6604 0         0 $new .= substr($params->{'image'}, ($line * $yl) + $index, $w * $bytes);
6605             }
6606 0         0 $params->{'image'} = $new;
6607 0         0 $params->{'width'} = $w;
6608 0         0 $params->{'x'} = $xclip;
6609             }
6610 0         0 $XX = $params->{'x'} + $params->{'width'};
6611 0 0       0 if ($XX > $xxclip) { # Right
6612 0         0 my $line = $params->{'width'} * $bytes;
6613 0         0 my $new = '';
6614 0         0 my $w = $xxclip - $params->{'x'};
6615 0         0 foreach my $yl (0 .. ($params->{'height'} - 1)) {
6616 0         0 $new .= substr($params->{'image'}, $line * $yl, $w * $bytes);
6617             }
6618 0         0 $params->{'image'} = $new;
6619 0         0 $params->{'width'} = $w;
6620             }
6621              
6622 0         0 my $size = ($params->{'width'} * $params->{'height'}) * $bytes;
6623 0 0       0 if (length($params->{'image'}) < $size) {
    0          
6624 0         0 $params->{'image'} .= chr(0) x ($size - length($params->{'image'}));
6625             } elsif (length($params->{'image'}) > $size) {
6626 0         0 $params->{'image'} = substr($params->{'image'}, 0, $size);
6627             }
6628 0         0 return ($params);
6629             }
6630              
6631             sub blit_transform {
6632             =head2 blit_transform
6633              
6634             This performs transformations on your blit objects.
6635              
6636             You can only have one of "rotate", "scale", "merge", "flip", or make "monochrome". You may use only one transformation per call.
6637              
6638             =head3 B (mandatory)
6639              
6640             Used by all transformations. It's the image data to process, in the format that "blit_write" uses. See the example below.
6641              
6642             =head3 B
6643              
6644             Flips the image either "horizontally, "vertically, or "both"
6645              
6646             =head3 B
6647              
6648             Merges one image on top of the other. "blit_data" is the top image, and "dest_blit_data" is the background image. This takes into account alpha data values for each pixel (if in 32 bit mode).
6649              
6650             This is very usefull in 32 bit mode due to its alpha channel capabilities.
6651              
6652             =head3 B
6653              
6654             Rotates the "blit_data" image an arbitrary degree. Positive degree values are counterclockwise and negative degree values are clockwise.
6655              
6656             Two types of rotate methods are available, an extrememly fast, but visually slightly less appealing method, and a slower, but looks better, method. Seriously though, the fast method looks pretty darn good anyway. I recommend "fast".
6657              
6658             =head3 B
6659              
6660             Scales the image to "width" x "height". This is the same as how scale works in "load_image". The "type" value tells it how to scale (see the example).
6661              
6662             =over 4
6663              
6664             $fb->blit_transform(
6665             {
6666             # blit_data is mandatory
6667             'blit_data' => { # Same as what blit_read or load_image returns
6668             'x' => 0, # This is relative to the dimensions of "dest_blit_data" for "merge"
6669             'y' => 0, # ^^
6670             'width' => 300,
6671             'height' => 200,
6672             'image' => $image_data
6673             },
6674              
6675             'merge' => {
6676             'dest_blit_data' => { # MUST have same or greater dimensions as 'blit_data'
6677             'x' => 0,
6678             'y' => 0,
6679             'width' => 300,
6680             'height' => 200,
6681             'image' => $image_data
6682             }
6683             },
6684              
6685             'rotate' => {
6686             'degrees' => 45, # 0-360 degrees. Negative numbers rotate clockwise.
6687             'quality' => 'high', # "high" or "fast" are your choices, with "fast" being the default
6688             },
6689              
6690             'flip' => 'horizontal', # or "vertical" or "both"
6691              
6692             'scale' => {
6693             'x' => 0,
6694             'y' => 0,
6695             'width' => 500,
6696             'height' => 300,
6697             'scale_type' => 'min' # 'min' = The smaller of the two
6698             # sizes are used (default)
6699             # 'max' = The larger of the two
6700             # sizes are used
6701             # 'nonprop' = Non-proportional sizing
6702             # The image is scaled to
6703             # width x height exactly.
6704             },
6705              
6706             'monochrome' => TRUE # Makes the image data monochrome
6707             }
6708             );
6709              
6710             =back
6711              
6712             It returns the transformed image in the same format the other BLIT methods use. Note, the width and height may be changed! So always use the returned data as the correct new data.
6713              
6714             =over 4
6715              
6716             {
6717             'x' => 0, # copied from "blit_data"
6718             'y' => 0, # copied from "blit_data"
6719             'width' => 100, # width of transformed image data
6720             'height' => 100, # height of transformed image data
6721             'image' => $image # image data
6722             }
6723              
6724             =back
6725              
6726             * Rotate and Flip is affected by the acceleration setting.
6727              
6728             =cut
6729              
6730 0     0 0 0 my $self = shift;
6731 0         0 my $params = shift;
6732              
6733 0         0 my $width = $params->{'blit_data'}->{'width'};
6734 0         0 my $height = $params->{'blit_data'}->{'height'};
6735 0         0 my $bytes = $self->{'BYTES'};
6736 0         0 my $bline = $width * $bytes;
6737 0         0 my $image = $params->{'blit_data'}->{'image'};
6738 0         0 my $xclip = $self->{'X_CLIP'};
6739 0         0 my $yclip = $self->{'Y_CLIP'};
6740 0         0 my $data;
6741              
6742 0 0       0 if (exists($params->{'merge'})) {
6743 0 0       0 $image = $self->_convert_16_to_24($image, RGB) if ($self->{'BITS'} == 16);
6744 0         0 eval {
6745 0         0 my $img = Imager->new();
6746 0         0 $img->read(
6747             'xsize' => $width,
6748             'ysize' => $height,
6749             'raw_datachannels' => max(3, $bytes),
6750             'raw_storechannels' => max(3, $bytes),
6751             'raw_interleave' => FALSE,
6752             'data' => $image,
6753             'type' => 'raw',
6754             'allow_incomplete' => TRUE
6755             );
6756 0         0 my $dest = Imager->new();
6757             $dest->read(
6758             'xsize' => $params->{'merge'}->{'dest_blit_data'}->{'width'},
6759             'ysize' => $params->{'merge'}->{'dest_blit_data'}->{'height'},
6760             'raw_datachannels' => max(3, $bytes),
6761             'raw_storechannels' => max(3, $bytes),
6762             'raw_interleave' => FALSE,
6763 0         0 'data' => $params->{'merge'}->{'dest_blit_data'}->{'image'},
6764             'type' => 'raw',
6765             'allow_incomplete' => TRUE
6766             );
6767             $dest->compose(
6768             'src' => $img,
6769             'tx' => $params->{'blit_data'}->{'x'},
6770 0         0 'ty' => $params->{'blit_data'}->{'y'},
6771             );
6772 0         0 $width = $dest->getwidth();
6773 0         0 $height = $dest->getheight();
6774 0         0 $dest->write(
6775             'type' => 'raw',
6776             'datachannels' => max(3, $bytes),
6777             'storechannels' => max(3, $bytes),
6778             'interleave' => FALSE,
6779             'data' => \$data
6780             );
6781             };
6782 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
6783              
6784 0 0       0 $data = $self->_convert_24_to_16($data, RGB) if ($self->{'BITS'} == 16);
6785             return (
6786             {
6787             'x' => $params->{'merge'}->{'dest_blit_data'}->{'x'},
6788 0         0 'y' => $params->{'merge'}->{'dest_blit_data'}->{'y'},
6789             'width' => $width,
6790             'height' => $height,
6791             'image' => $data
6792             }
6793             );
6794             }
6795 0 0       0 if (exists($params->{'flip'})) {
    0          
    0          
    0          
    0          
6796 0         0 my $image = "$params->{'blit_data'}->{'image'}";
6797 0         0 my $new = '';
6798 0 0       0 if ($self->{'ACCELERATED'}) {
6799 0         0 $new = "$image";
6800 0 0       0 if (lc($params->{'flip'}) eq 'vertical') {
    0          
    0          
6801 0         0 c_flip_vertical($new, $width, $height, $bytes);
6802             } elsif (lc($params->{'flip'}) eq 'horizontal') {
6803 0         0 c_flip_horizontal($new, $width, $height, $bytes);
6804             } elsif (lc($params->{'flip'}) eq 'both') {
6805 0         0 c_flip_both($new, $width, $height, $bytes);
6806             }
6807             } else {
6808 0 0       0 if (lc($params->{'flip'}) eq 'vertical') {
    0          
6809 0         0 for (my $y = ($height - 1); $y >= 0; $y--) {
6810 0         0 $new .= substr($image, ($y * $bline), $bline);
6811             }
6812             } elsif (lc($params->{'flip'}) eq 'horizontal') {
6813 0         0 foreach my $y (0 .. ($height - 1)) {
6814 0         0 for (my $x = ($width - 1); $x >= 0; $x--) {
6815 0         0 $new .= substr($image, (($x * $bytes) + ($y * $bline)), $bytes);
6816             }
6817             }
6818             } else {
6819 0         0 $new = "$image";
6820             }
6821             }
6822             return (
6823             {
6824             'x' => $params->{'blit_data'}->{'x'},
6825 0         0 'y' => $params->{'blit_data'}->{'y'},
6826             'width' => $width,
6827             'height' => $height,
6828             'image' => $new
6829             }
6830             );
6831             } elsif (exists($params->{'rotate'})) {
6832 0         0 my $degrees = $params->{'rotate'}->{'degrees'};
6833 0         0 while (abs($degrees) > 360) { # normalize
6834 0 0       0 if ($degrees > 360) {
6835 0         0 $degrees -= 360;
6836             } else {
6837 0         0 $degrees += 360;
6838             }
6839             }
6840 0 0 0     0 return ($params->{'blit_data'}) if (abs($degrees) == 360 || $degrees == 0); # 0 and 360 are not a rotation
6841 0 0 0     0 unless ($params->{'rotate'}->{'quality'} eq 'high' || $self->{'ACCELERATED'} == PERL) {
6842 0 0       0 if (abs($degrees) == 180) {
6843 0         0 my $new = "$image";
6844 0         0 c_flip_both($new, $width, $height, $bytes);
6845             return (
6846             {
6847             'x' => $params->{'blit_data'}->{'x'},
6848 0         0 'y' => $params->{'blit_data'}->{'y'},
6849             'width' => $width,
6850             'height' => $height,
6851             'image' => $new
6852             }
6853             );
6854             } else {
6855 0         0 my $wh = int(sqrt($width**2 + $height**2) + .5);
6856              
6857             # Try to define as much as possible before the loop to optimize
6858 0         0 $data = $self->{'RAW_BACKGROUND_COLOR'} x (($wh**2) * $bytes);
6859              
6860 0         0 c_rotate($image, $data, $width, $height, $wh, $degrees, $bytes);
6861             return (
6862             {
6863             'x' => $params->{'blit_data'}->{'x'},
6864 0         0 'y' => $params->{'blit_data'}->{'y'},
6865             'width' => $wh,
6866             'height' => $wh,
6867             'image' => $data
6868             }
6869             );
6870             }
6871             } else {
6872 0         0 eval {
6873 0         0 my $img = Imager->new();
6874 0 0       0 $image = $self->_convert_16_to_24($image, RGB) if ($self->{'BITS'} == 16);
6875 0         0 $img->read(
6876             'xsize' => $width,
6877             'ysize' => $height,
6878             'raw_storechannels' => max(3, $bytes),
6879             'raw_datachannels' => max(3, $bytes),
6880             'raw_interleave' => FALSE,
6881             'data' => $image,
6882             'type' => 'raw',
6883             'allow_incomplete' => TRUE
6884             );
6885 0         0 my $rotated;
6886 0 0 0     0 if (abs($degrees) == 90 || abs($degrees) == 180 || abs($degrees) == 270) {
      0        
6887 0         0 $rotated = $img->rotate('right' => 0 - $degrees, 'back' => $self->{'IMAGER_BACKGROUND_COLOR'});
6888             } else {
6889 0         0 $rotated = $img->rotate('degrees' => 0 - $degrees, 'back' => $self->{'IMAGER_BACKGROUND_COLOR'});
6890             }
6891 0         0 $width = $rotated->getwidth();
6892 0         0 $height = $rotated->getheight();
6893 0         0 $img = $rotated;
6894 0         0 $img->write(
6895             'type' => 'raw',
6896             'storechannels' => max(3, $bytes),
6897             'interleave' => FALSE,
6898             'data' => \$data
6899             );
6900 0 0       0 $data = $self->_convert_24_to_16($data, RGB) if ($self->{'BITS'} == 16);
6901             };
6902 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
6903             }
6904             return (
6905             {
6906             'x' => $params->{'blit_data'}->{'x'},
6907 0         0 'y' => $params->{'blit_data'}->{'y'},
6908             'width' => $width,
6909             'height' => $height,
6910             'image' => $data
6911             }
6912             );
6913             } elsif (exists($params->{'scale'})) {
6914 0 0       0 $image = $self->_convert_16_to_24($image, $self->{'COLOR_ORDER'}) if ($self->{'BITS'} == 16);
6915              
6916 0         0 eval {
6917 0         0 my $img = Imager->new();
6918 0         0 $img->read(
6919             'xsize' => $width,
6920             'ysize' => $height,
6921             'raw_storechannels' => max(3, $bytes),
6922             'raw_datachannels' => max(3, $bytes),
6923             'raw_interleave' => FALSE,
6924             'data' => $image,
6925             'type' => 'raw',
6926             'allow_incomplete' => TRUE
6927             );
6928              
6929 0 0       0 $img = $img->convert('preset' => 'addalpha') if ($self->{'BITS'} == 32);
6930             my %scale = (
6931             'xpixels' => $params->{'scale'}->{'width'},
6932             'ypixels' => $params->{'scale'}->{'height'},
6933 0   0     0 'type' => $params->{'scale'}->{'scale_type'} || 'min'
6934             );
6935 0         0 my ($xs, $ys);
6936              
6937 0         0 ($xs, $ys, $width, $height) = $img->scale_calculate(%scale);
6938 0         0 my $scaledimg = $img->scale(%scale);
6939 0         0 $scaledimg->write(
6940             'type' => 'raw',
6941             'storechannels' => max(3, $bytes),
6942             'interleave' => FALSE,
6943             'data' => \$data
6944             );
6945             };
6946 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
6947 0 0       0 $data = $self->_convert_24_to_16($data, $self->{'COLOR_ORDER'}) if ($self->{'BITS'} == 16);
6948             return (
6949             {
6950             'x' => $params->{'blit_data'}->{'x'},
6951 0         0 'y' => $params->{'blit_data'}->{'y'},
6952             'width' => $width,
6953             'height' => $height,
6954             'image' => $data
6955             }
6956             );
6957             } elsif (exists($params->{'monochrome'})) {
6958 0         0 return ($self->monochrome({ 'image' => $params->{'blit_data'}, 'bits' => $self->{'BITS'} }));
6959             } elsif (exists($params->{'center'})) {
6960 0         0 my $XX = $self->{'W_CLIP'};
6961 0         0 my $YY = $self->{'H_CLIP'};
6962 0         0 my ($x, $y) = ($params->{'blit_data'}->{'x'}, $params->{'blit_data'}->{'y'});
6963 0 0 0     0 if ($params->{'center'} == CENTER_X || $params->{'center'} == CENTER_XY) {
6964 0         0 $x = $xclip + int(($XX - $width) / 2);
6965             }
6966 0 0 0     0 if ($params->{'center'} == CENTER_Y || $params->{'center'} == CENTER_XY) {
6967 0         0 $y = $self->{'Y_CLIP'} + int(($YY - $height) / 2);
6968             }
6969             return (
6970             {
6971             'x' => $x,
6972             'y' => $y,
6973             'width' => $width,
6974             'height' => $height,
6975 0         0 'image' => $params->{'blit_data'}->{'image'}
6976             }
6977             );
6978              
6979             }
6980             }
6981              
6982             sub clip_reset {
6983             =head2 clip_reset
6984              
6985             Turns off clipping, and resets the clipping values to the full size of the screen.
6986              
6987             =over 4
6988              
6989             $fb->clip_reset();
6990              
6991             =back
6992             =cut
6993              
6994             # Clipping is not really turned off. It's just set to the screen borders. To turn off clipping for real is asking for crashes.
6995 2     2 0 8 my $self = shift;
6996              
6997 2         6 $self->{'X_CLIP'} = 0;
6998 2         6 $self->{'Y_CLIP'} = 0;
6999 2         6 $self->{'XX_CLIP'} = ($self->{'XRES'} - 1);
7000 2         6 $self->{'YY_CLIP'} = ($self->{'YRES'} - 1);
7001 2         36 $self->{'W_CLIP'} = $self->{'XX_CLIP'} - $self->{'X_CLIP'};
7002 2         23 $self->{'H_CLIP'} = $self->{'YY_CLIP'} - $self->{'Y_CLIP'};
7003 2         9 $self->{'CLIPPED'} = FALSE; ## This is merely a flag to see if a clipping
7004             ## region is defined under the screen dimensions.
7005             }
7006              
7007             sub clip_off {
7008             =head2 clip_off
7009              
7010             This is an alias to 'clip_reset'
7011              
7012             =cut
7013              
7014 0     0 0 0 my $self = shift;
7015 0         0 $self->clip_reset();
7016             }
7017              
7018             sub clip_set {
7019             =head2 clip_set
7020              
7021             Sets the clipping rectangle starting at the top left point x,y and ending at bottom right point xx,yy.
7022              
7023             =over 4
7024              
7025             $fb->clip_set({
7026             'x' => 10,
7027             'y' => 10,
7028             'xx' => 300,
7029             'yy' => 300
7030             });
7031              
7032             =back
7033             =cut
7034              
7035 0     0 0 0 my $self = shift;
7036 0         0 my $params = shift;
7037              
7038 0         0 $self->{'X_CLIP'} = abs(int($params->{'x'}));
7039 0         0 $self->{'Y_CLIP'} = abs(int($params->{'y'}));
7040 0         0 $self->{'XX_CLIP'} = abs(int($params->{'xx'}));
7041 0         0 $self->{'YY_CLIP'} = abs(int($params->{'yy'}));
7042              
7043 0 0       0 $self->{'X_CLIP'} = ($self->{'XRES'} - 2) if ($self->{'X_CLIP'} > ($self->{'XRES'} - 1));
7044 0 0       0 $self->{'Y_CLIP'} = ($self->{'YRES'} - 2) if ($self->{'Y_CLIP'} > ($self->{'YRES'} - 1));
7045 0 0       0 $self->{'XX_CLIP'} = ($self->{'XRES'} - 1) if ($self->{'XX_CLIP'} >= $self->{'XRES'});
7046 0 0       0 $self->{'YY_CLIP'} = ($self->{'YRES'} - 1) if ($self->{'YY_CLIP'} >= $self->{'YRES'});
7047 0         0 $self->{'W_CLIP'} = $self->{'XX_CLIP'} - $self->{'X_CLIP'};
7048 0         0 $self->{'H_CLIP'} = $self->{'YY_CLIP'} - $self->{'Y_CLIP'};
7049 0         0 $self->{'CLIPPED'} = TRUE;
7050             }
7051              
7052             sub clip_rset {
7053             =head2 clip_rset
7054              
7055             Sets the clipping rectangle to point x,y,width,height
7056              
7057             =over 4
7058              
7059             $fb->clip_rset({
7060             'x' => 10,
7061             'y' => 10,
7062             'width' => 600,
7063             'height' => 400
7064             });
7065              
7066             =back
7067             =cut
7068              
7069 0     0 0 0 my $self = shift;
7070 0         0 my $params = shift;
7071              
7072 0         0 $params->{'xx'} = $params->{'x'} + $params->{'width'};
7073 0         0 $params->{'yy'} = $params->{'y'} + $params->{'height'};
7074              
7075 0         0 $self->clip_set($params);
7076             }
7077              
7078             sub monochrome {
7079             =head2 monochrome
7080              
7081             Removes all color information from an image, and leaves everything in greyscale.
7082              
7083             It applies the following formula to calculate greyscale:
7084              
7085             grey_color = (red * 0.2126) + (green * 0.7155) + (blue * 0.0722)
7086              
7087             =over 4
7088              
7089             Expects two parameters, 'image' and 'bits'. The parameter 'image' is a string containing the image data. The parameter 'bits' is how many bits per pixel make up the image. Valid values are 16, 24, and 32 only.
7090              
7091             $fb->monochrome({
7092             'image' => "image data",
7093             'bits' => 32
7094             });
7095              
7096             It returns 'image' back, but now in greyscale (still the same RGB format though).
7097              
7098             {
7099             'image' => "monochrome image data"
7100             }
7101              
7102             =back
7103              
7104             * You should normally use "blit_transform", but this is a more raw way of affecting the data
7105              
7106             =cut
7107              
7108 0     0 0 0 my $self = shift;
7109 0         0 my $params = shift;
7110              
7111 0         0 my ($r, $g, $b);
7112              
7113 0         0 my ($ro, $go, $bo) = ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}, $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}, $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'});
7114 0         0 my ($rl, $gl, $bl) = ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'}, $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'}, $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});
7115              
7116 0         0 my $color_order = $self->{'COLOR_ORDER'};
7117 0         0 my $size = length($params->{'image'});
7118              
7119 0         0 my $inc;
7120 0 0       0 if ($params->{'bits'} == 32) {
    0          
    0          
7121 0         0 $inc = 4;
7122             } elsif ($params->{'bits'} == 24) {
7123 0         0 $inc = 3;
7124             } elsif ($params->{'bits'} == 16) {
7125 0         0 $inc = 2;
7126             } else { # Only 32, 24, or 16 bits allowed
7127 0         0 return ();
7128             }
7129 0 0       0 if ($self->{'ACCELERATED'}) {
7130 0         0 c_monochrome($params->{'image'}, $size, $color_order, $inc);
7131 0         0 return ($params->{'image'});
7132             } else {
7133 0         0 for (my $byte = 0; $byte < length($params->{'image'}); $byte += $inc) {
7134 0 0       0 if ($inc == 2) {
7135 0         0 my $rgb565 = unpack('S', substr($params->{'image'}, $byte, $inc));
7136 0 0       0 if ($color_order == RGB) {
    0          
7137 0         0 $r = $rgb565 & 31;
7138 0         0 $g = (($rgb565 >> 5) & 63) / 2; # Normalize green
7139 0         0 $b = ($rgb565 >> 11) & 31;
7140             } elsif ($color_order == BGR) {
7141 0         0 $b = $rgb565 & 31;
7142 0         0 $g = (($rgb565 >> 5) & 63) / 2; # Normalize green
7143 0         0 $r = ($rgb565 >> 11) & 31;
7144             }
7145 0         0 my $mono = int(0.2126 * $r + 0.7155 * $g + 0.0722 * $b);
7146 0 0       0 substr($params->{'image'}, $byte, $inc) = pack('S', ($go ? ($mono * 2) << $go : ($mono * 2)) | ($ro ? $mono << $ro : $mono) | ($bo ? $mono << $bo : $mono));
    0          
    0          
7147             } else {
7148 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
7149 0         0 ($b, $g, $r) = unpack('C3', substr($params->{'image'}, $byte, 3));
7150             } elsif ($color_order == BRG) {
7151 0         0 ($b, $r, $g) = unpack('C3', substr($params->{'image'}, $byte, 3));
7152             } elsif ($color_order == RGB) {
7153 0         0 ($r, $g, $b) = unpack('C3', substr($params->{'image'}, $byte, 3));
7154             } elsif ($color_order == RBG) {
7155 0         0 ($r, $b, $g) = unpack('C3', substr($params->{'image'}, $byte, 3));
7156             } elsif ($color_order == GRB) {
7157 0         0 ($g, $r, $b) = unpack('C3', substr($params->{'image'}, $byte, 3));
7158             } elsif ($color_order == GBR) {
7159 0         0 ($g, $b, $r) = unpack('C3', substr($params->{'image'}, $byte, 3));
7160             }
7161 0         0 my $mono = int(0.2126 * $r + 0.7155 * $g + 0.0722 * $b);
7162 0         0 substr($params->{'image'}, $byte, 3) = pack('C3', $mono, $mono, $mono);
7163             }
7164             }
7165             }
7166 0         0 return ($params->{'image'});
7167             }
7168              
7169             sub ttf_print {
7170             =head2 ttf_print
7171              
7172             Prints TrueType text on the screen at point x,y in the rectangle width,height, using the color 'color', and the face 'face' (using the Imager library as its engine).
7173              
7174             Note, 'y' is the baseline position, not the top left of the bounding box. This is a change from before!!!
7175              
7176             This is best called twice, first in bounding box mode, and then in normal mode.
7177              
7178             Bounding box mode gets the actual values needed to display the text.
7179              
7180             If draw mode is "normal", then mask mode is automatically used for best output.
7181              
7182             =over 4
7183              
7184             my $bounding_box = $fb->ttf_print({
7185             'x' => 20,
7186             'y' => 100, # baseline position
7187             'height' => 16,
7188             'wscale' => 1, # Scales the width. 1 is normal
7189             'color' => 'FFFF00FF', # Hex value of color 00-FF (RRGGBBAA)
7190             'text' => 'Hello World!',
7191             'font_path' => '/usr/share/fonts/truetype', # Optional
7192             'face' => 'Arial.ttf', # Optional
7193             'bounding_box' => TRUE,
7194             'center' => CENTER_X,
7195             'antialias' => TRUE
7196             });
7197              
7198             $fb->ttf_print($bounding_box);
7199              
7200             =back
7201              
7202             Here's a shortcut:
7203              
7204             =over 4
7205              
7206             $fb->ttf_print(
7207             $fb->ttf_print({
7208             'x' => 20,
7209             'y' => 100, # baseline position
7210             'height' => 16,
7211             'color' => 'FFFF00FF', # RRGGBBAA
7212             'text' => 'Hello World!',
7213             'font_path' => '/usr/share/fonts/truetype', # Optional
7214             'face' => 'Arial.ttf', # Optional
7215             'bounding_box' => TRUE,
7216             'rotate' => 45, # optonal
7217             'center' => CENTER_X,
7218             'antialias' => 1,
7219             'shadow' => shadow size
7220             })
7221             );
7222              
7223             =back
7224              
7225             Failures of this method are usually due to it not being able to find the font. Make sure you have the right path and name.
7226              
7227             =cut
7228              
7229             ##############################################################################
7230             # Yes, this is a "hack". #
7231             # -------------------------------------------------------------------------- #
7232             # This uses the 'Imager' package. It allocates a temporary screen buffer #
7233             # and prints to it, then this buffer is dumped to the screen at the x,y #
7234             # coordinates given. Since no decent True Type packages or libraries are #
7235             # available for Perl, this turned out to be the best and easiest solution. #
7236             ##############################################################################
7237 4     4 0 147 my $self = shift;
7238 4         16 my $params = shift;
7239              
7240 4 100       32 return ($params) unless (defined($params));
7241              
7242 2   50     54 my $TTF_x = int($params->{'x'}) || 0;
7243 2   50     32 my $TTF_y = int($params->{'y'}) || 0;
7244 2   50     17 my $TTF_pw = int($params->{'pwidth'}) || 6;
7245 2   50     24 my $TTF_ph = int($params->{'pheight'}) || 6;
7246 2   50     22 my $TTF_h = int($params->{'height'}) || 6;
7247 2   50     19 my $text = $params->{'text'} || ' ';
7248 2   33     25 my $face = $params->{'face'} || $self->{'FONT_FACE'};
7249 2   50     13 my $box_mode = $params->{'bounding_box'} || FALSE;
7250 2   50     13 my $center_mode = $params->{'center'} || 0;
7251 2   33     30 my $font_path = $params->{'font_path'} || $self->{'FONT_PATH'};
7252 2   50     8 my $aa = $params->{'antialias'} || FALSE;
7253 2 50       9 my $P_color = $params->{'color'} if (exists($params->{'color'}));
7254 2         4 my $sizew = $TTF_h;
7255 2 50 33     21 $sizew *= $params->{'wscale'} if (exists($params->{'wscale'}) && defined($params->{'wscale'}));
7256 2         22 my $pfont = "$font_path/$face";
7257              
7258 2         71 $pfont =~ s#/+#/#g; # Get rid of doubled up slashes
7259              
7260 2         8 my $color_order = $self->{'COLOR_ORDER'};
7261 2         5 my $bytes = $self->{'BYTES'};
7262 2         14 my ($data, $shadow_font, $neg_width, $global_descent, $pos_width, $global_ascent, $descent, $ascent, $advance_width, $right_bearing); # = ('','',0,0,0,0,0,0,0,0);
7263              
7264 2 50       15 if (defined($P_color)) {
7265 2 50       13 $P_color .= 'FF' if (length($P_color) < 8); # Add opague alpha if it is not defined
7266 2         13 my ($red, $green, $blue, $alpha) = (substr($P_color, 0, 2), substr($P_color, 2, 2), substr($P_color, 4, 2), substr($P_color, 6, 2));
7267 2 50       15 if ($color_order == BGR) {
    50          
    50          
    50          
    50          
7268 0         0 $P_color = $blue . $green . $red . $alpha;
7269             } elsif ($color_order == BRG) {
7270 0         0 $P_color = $blue . $red . $green . $alpha;
7271             } elsif ($color_order == RBG) {
7272 0         0 $P_color = $red . $blue . $green . $alpha;
7273             } elsif ($color_order == GRB) {
7274 0         0 $P_color = $green . $red . $blue . $alpha;
7275             } elsif ($color_order == GBR) {
7276 0         0 $P_color = $green . $blue . $red . $alpha;
7277             }
7278             } else {
7279 0         0 $P_color = $self->{'IMAGER_FOREGROUND_COLOR'};
7280             }
7281              
7282 2         55 my $font = Imager::Font->new(
7283             'file' => $pfont,
7284             'color' => $P_color,
7285             'size' => $TTF_h
7286             );
7287 2 50       1077 unless (defined($font)) {
7288 2 50       14 warn __LINE__ . " Can't initialize Imager::Font!\n", Imager->errstr() if ($self->{'SHOW_ERRORS'});
7289 2         14 return (undef);
7290             }
7291 0 0 0     0 if (defined($params->{'rotate'}) && abs($params->{'rotate'}) > 0 && abs($params->{'rotate'} < 360)) {
      0        
7292 0         0 my $matrix;
7293 0         0 eval {
7294 0         0 $matrix = Imager::Matrix2d->rotate('degrees' => $params->{'rotate'});
7295 0         0 $font->transform('matrix' => $matrix);
7296 0         0 my $bbox = $font->bounding_box('string' => $text, 'canon' => 1, 'size' => $TTF_h, 'sizew' => $sizew);
7297 0         0 my ($left, $miny, $right, $maxy) = _transformed_bounds($bbox, $matrix);
7298 0         0 my ($top, $bottom) = (-$maxy, -$miny);
7299 0         0 ($TTF_pw, $TTF_ph) = ($right - $left, $bottom - $top);
7300 0         0 $params->{'pwidth'} = $TTF_pw;
7301 0         0 $params->{'pheight'} = $TTF_ph;
7302             };
7303 0 0 0     0 warn __LINE__ . " $@\n", Imager->errstr() if ($@ && $self->{'SHOW_ERRORS'});
7304             } else {
7305 0         0 eval { ($neg_width, $global_descent, $pos_width, $global_ascent, $descent, $ascent, $advance_width, $right_bearing) = $font->bounding_box('string' => $text, 'canon' => 1, 'size' => $TTF_h, 'sizew' => $sizew); };
  0         0  
7306 0 0       0 if ($@) {
7307 0 0       0 warn __LINE__ . " $@\n", Imager->errstr() if ($self->{'SHOW_ERRORS'});
7308 0         0 return (undef);
7309             }
7310 0         0 $params->{'pwidth'} = $advance_width;
7311 0         0 $params->{'pheight'} = abs($global_ascent) + abs($global_descent) + 12; # int($TTF_h + $global_ascent + abs($global_descent));
7312 0         0 $TTF_pw = abs($advance_width);
7313             }
7314 0 0       0 if ($center_mode == CENTER_XY) {
    0          
    0          
7315 0         0 $TTF_x = int(($self->{'W_CLIP'} - $TTF_pw) / 2) + $self->{'X_CLIP'};
7316 0         0 $TTF_y = int(($self->{'H_CLIP'} - $TTF_ph) / 2) + abs($global_ascent);
7317             } elsif ($center_mode == CENTER_X) {
7318 0         0 $TTF_x = int(($self->{'W_CLIP'} - $TTF_pw) / 2) + $self->{'X_CLIP'};
7319             } elsif ($center_mode == CENTER_Y) {
7320 0         0 $TTF_y = int(($self->{'H_CLIP'} - $TTF_ph) / 2) + abs($global_ascent);
7321             }
7322 0         0 $params->{'bounding_box'} = FALSE;
7323 0 0       0 if ($box_mode) {
7324 0         0 $params->{'x'} = $TTF_x;
7325 0         0 $params->{'y'} = $TTF_y;
7326 0         0 return ($params);
7327             }
7328 0         0 my $img;
7329             my $image;
7330 0         0 my $draw_mode;
7331 0 0 0     0 if ($TTF_pw <= 0 || $TTF_ph <= 0) {
7332 0 0       0 warn __LINE__ . " Calculated size of font width/height is less than or equal to zero! Cannot render font." if ($self->{'SHOW_ERRORS'});
7333 0         0 return (undef);
7334             }
7335 0         0 eval {
7336 0         0 $img = Imager->new(
7337             'xsize' => $TTF_pw,
7338             'ysize' => $TTF_ph,
7339             'channels' => max(3, $bytes)
7340             );
7341 0 0       0 unless ($self->{'DRAW_MODE'}) {
7342 0 0 0     0 if ($self->{'ACCELERATED'} && !$aa) {
7343 0         0 $draw_mode = $self->{'DRAW_MODE'};
7344 0         0 $self->{'DRAW_MODE'} = MASK_MODE;
7345             } else {
7346 0         0 my $ty = $TTF_y - abs($global_ascent);
7347 0 0       0 $ty = 0 if ($ty < 0);
7348 0         0 $image = $self->blit_read({ 'x' => $TTF_x, 'y' => $ty, 'width' => $TTF_pw, 'height' => $TTF_ph });
7349 0 0       0 $image->{'image'} = $self->_convert_16_to_24($image->{'image'}, RGB) if ($self->{'BITS'} == 16);
7350             $img->read(
7351 0         0 'data' => $image->{'image'},
7352             'type' => 'raw',
7353             'raw_datachannels' => max(3, $bytes),
7354             'raw_storechannels' => max(3, $bytes),
7355             'raw_interleave' => FALSE,
7356             'xsize' => $TTF_pw,
7357             'ysize' => $TTF_ph
7358             );
7359             }
7360             }
7361             $img->string(
7362 0         0 'font' => $font,
7363             'text' => $text,
7364             'x' => 0,
7365             'y' => abs($ascent),
7366             'size' => $TTF_h,
7367             'sizew' => $sizew,
7368             'color' => $P_color,
7369             'aa' => $aa,
7370             );
7371 0         0 $img->write(
7372             'type' => 'raw',
7373             'storechannels' => max(3, $bytes), # Must be at least 24 bit
7374             'interleave' => FALSE,
7375             'data' => \$data
7376             );
7377             };
7378 0 0       0 if ($@) {
7379 0 0       0 warn __LINE__ . " ERROR $@\n", Imager->errstr() . "\n$TTF_pw,$TTF_ph" if ($self->{'SHOW_ERRORS'});
7380 0         0 return (undef);
7381             }
7382 0 0       0 $data = $self->_convert_24_to_16($data, RGB) if ($self->{'BITS'} == 16);
7383 0         0 $self->blit_write({ 'x' => $TTF_x, 'y' => ($TTF_y - abs($global_ascent)), 'width' => $TTF_pw, 'height' => $TTF_ph, 'image' => $data });
7384 0 0       0 $self->{'DRAW_MODE'} = $draw_mode if (defined($draw_mode));
7385 0         0 return ($params);
7386             }
7387              
7388             sub ttf_paragraph {
7389             =head2 ttf_paragraph
7390              
7391             Very similar to an ordinary Perl "print", but uses TTF fonts instead. It will automatically wrap text like a terminal.
7392              
7393             This uses no bounding boxes, and is only needed to be called once. It uses a very simple wrapping model.
7394              
7395             It uses the clipping rectangle. All text will be fit and wrapped within the clipping rectangle.
7396              
7397             Text is started at "x" and wrapped to "x" for each line, no indentation.
7398              
7399             * This does NOT scroll text. It merely truncates what doesn't fit. It returns where in the text string it last printed before truncation. It's also quite slow.
7400              
7401             =over 4
7402              
7403             $fb->ttf_paragraph(
7404             {
7405             'text' => 'String to print',
7406              
7407             'x' => 0, # Where to start printing
7408             'y' => 20, #
7409              
7410             'size' => 12, # Optional Font size, default is 16
7411              
7412             'color' => 'FFFF00FF', # RRGGBBAA
7413              
7414             'justify' => 'justified' # Optional justification, default
7415             # is "left". Posible values are:
7416             # "left", "right", "center", and
7417             # "justified"
7418              
7419             'line_spacing' => 5, # This adjusts the default line
7420             # spacing by positive or negative
7421             # amounts. The default is 0.
7422              
7423             'face' => 'Ariel', # Optional, overrides the default
7424              
7425             'font_path' => '/usr/share/fonts', # Optional, else uses the default
7426             }
7427             );
7428              
7429             =back
7430              
7431             =cut
7432              
7433 0     0 0 0 my $self = shift;
7434 0         0 my $params = shift;
7435              
7436 0 0       0 return ($params) unless (defined($params));
7437              
7438 0   0     0 my $TTF_x = int($params->{'x'}) || 0;
7439 0   0     0 my $TTF_y = int($params->{'y'}) || 0;
7440 0   0     0 my $TTF_size = int($params->{'size'}) || 16;
7441 0   0     0 my $text = $params->{'text'} || ' ';
7442 0   0     0 my $face = $params->{'face'} || $self->{'FONT_FACE'};
7443 0   0     0 my $justify = $params->{'justify'} || 'left';
7444 0         0 $justify =~ s/centre/center/; # Wacky Brits and Canadians
7445 0   0     0 my $linegap = int($params->{'line_spacing'}) || 0;
7446 0   0     0 my $font_path = $params->{'font_path'} || $self->{'FONT_PATH'};
7447 0 0       0 my $P_color = $params->{'color'} if (exists($params->{'color'}));
7448 0         0 my $pfont = "$font_path/$face";
7449              
7450 0         0 $TTF_x -= $self->{'X_CLIP'};
7451 0         0 $TTF_y -= $self->{'Y_CLIP'};
7452 0         0 $justify = lc($justify);
7453 0         0 $justify =~ s/justified/fill/;
7454 0         0 $pfont =~ s#/+#/#g; # Get rid of doubled up slashes
7455              
7456 0         0 my $color_order = $self->{'COLOR_ORDER'};
7457 0         0 my $bytes = $self->{'BYTES'};
7458 0         0 my $data;
7459              
7460 0 0       0 if (defined($P_color)) {
7461 0 0       0 $P_color .= 'FF' if (length($P_color) < 8); # Add opague alpha if it is not defined
7462 0         0 my ($red, $green, $blue, $alpha) = (substr($P_color, 0, 2), substr($P_color, 2, 2), substr($P_color, 4, 2), substr($P_color, 6, 2));
7463 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
7464 0         0 $P_color = $blue . $green . $red . $alpha;
7465             } elsif ($color_order == BRG) {
7466 0         0 $P_color = $blue . $red . $green . $alpha;
7467             } elsif ($color_order == RBG) {
7468 0         0 $P_color = $red . $blue . $green . $alpha;
7469             } elsif ($color_order == GRB) {
7470 0         0 $P_color = $green . $red . $blue . $alpha;
7471             } elsif ($color_order == GBR) {
7472 0         0 $P_color = $green . $blue . $red . $alpha;
7473             }
7474             } else {
7475 0         0 $P_color = $self->{'IMAGER_FOREGROUND_COLOR'};
7476             }
7477              
7478 0         0 my $font = Imager::Font->new(
7479             'file' => $pfont,
7480             'color' => $P_color,
7481             );
7482 0 0       0 unless (defined($font)) {
7483 0 0       0 warn __LINE__ . " Can't initialize Imager::Font!\n", Imager->errstr() if ($self->{'SHOW_ERRORS'});
7484 0         0 return (undef);
7485             }
7486 0         0 my $img;
7487             my $image;
7488 0         0 my $draw_mode;
7489 0         0 my $savepos;
7490 0         0 eval {
7491             $img = Imager->new(
7492             'xsize' => $self->{'W_CLIP'},
7493 0         0 'ysize' => $self->{'H_CLIP'},
7494             'channels' => max(3, $bytes)
7495             );
7496 0 0       0 unless ($self->{'DRAW_MODE'}) { # If normal mode, then don't bother
7497 0 0       0 if ($self->{'ACCELERATED'}) {
7498 0         0 $draw_mode = $self->{'DRAW_MODE'};
7499 0         0 $self->{'DRAW_MODE'} = MASK_MODE;
7500             } else {
7501 0         0 $image = $self->blit_read({ 'x' => $self->{'X_CLIP'}, 'y' => $self->{'Y_CLIP'}, 'width' => $self->{'W_CLIP'}, 'height' => $self->{'H_CLIP'}});
7502 0 0       0 $image->{'image'} = $self->_convert_16_to_24($image->{'image'}, RGB) if ($self->{'BITS'} == 16);
7503             $img->read(
7504             'data' => $image->{'image'},
7505             'type' => 'raw',
7506             'raw_datachannels' => max(3, $bytes),
7507             'raw_storechannels' => max(3, $bytes),
7508             'raw_interleave' => FALSE,
7509             'xsize' => $self->{'W_CLIP'},
7510 0         0 'ysize' => $self->{'H_CLIP'},
7511             );
7512             }
7513             }
7514             Imager::Font::Wrap->wrap_text(
7515 0         0 'x' => $TTF_x,
7516             'y' => $TTF_y,
7517             'size' => $TTF_size,
7518             'string' => $text,
7519             'font' => $font,
7520             'image' => $img,
7521             'justify' => $justify,
7522             'linegap' => $linegap,
7523             'savepos' => \$savepos,
7524             );
7525 0         0 $img->write(
7526             'type' => 'raw',
7527             'storechannels' => max(3, $bytes), # Must be at least 24 bit
7528             'interleave' => FALSE,
7529             'data' => \$data
7530             );
7531             };
7532 0 0       0 if ($@) {
7533 0 0       0 warn __LINE__ . " ERROR $@\n", Imager->errstr() if ($self->{'SHOW_ERRORS'});
7534 0         0 return (undef);
7535             }
7536 0 0       0 $data = $self->_convert_24_to_16($data, RGB) if ($self->{'BITS'} == 16);
7537 0         0 $self->blit_write({ 'x' => $self->{'X_CLIP'}, 'y' => $self->{'Y_CLIP'}, 'width' => $self->{'W_CLIP'}, 'height' => $self->{'H_CLIP'}, 'image' => $data });
7538 0 0       0 $self->{'DRAW_MODE'} = $draw_mode if (defined($draw_mode));
7539 0         0 return ($savepos);
7540             }
7541              
7542             sub _gather_fonts {
7543             # Gather in and find all the fonts
7544 2     2   9 my $self = shift;
7545 2         5 my $path = shift;
7546              
7547 2         91 opendir(my $DIR, $path);
7548 2         14 chomp(my @dir = readdir($DIR));
7549 2         7 closedir($DIR);
7550              
7551 2         21 foreach my $file (@dir) {
7552 0 0       0 next if ($file =~ /^\./);
7553 0 0 0     0 if (-d "$path/$file") {
    0          
7554 0         0 $self->_gather_fonts("$path/$file");
7555             } elsif (-f "$path/$file" && -s "$path/$file") { # Makes sure font is non-zero length
7556 0 0 0     0 if ($file =~ /\.ttf$/i && ($self->{'Imager-Has-TrueType'} || $self->{'Imager-Has-Freetype2'})) {
    0 0        
      0        
7557 0         0 my $face = $self->get_face_name({ 'font_path' => $path, 'face' => $file });
7558 0         0 $self->{'FONTS'}->{$face} = { 'path' => $path, 'font' => $file };
7559             } elsif ($file =~ /\.afb$/i && $self->{'Imager-Has-Type1'}) {
7560 0         0 my $face = $self->get_face_name({ 'font_path' => $path, 'face' => $file });
7561 0         0 $self->{'FONTS'}->{$face} = { 'path' => $path, 'font' => $file };
7562             }
7563             }
7564             }
7565             }
7566              
7567             sub get_face_name {
7568             =head2 get_face_name
7569              
7570             Returns the TrueType face name based on the parameters passed.
7571              
7572             my $face_name = $fb->get_face_name({
7573             'font_path' => '/usr/share/fonts/TrueType/',
7574             'face' => 'FontFileName.ttf'
7575             });
7576              
7577             =cut
7578              
7579 0     0 0 0 my $self = shift;
7580 0         0 my $params = shift;
7581              
7582 0         0 my $file = $params->{'font_path'} . '/' . $params->{'face'};
7583 0         0 my $face = Imager::Font->new('file' => $file);
7584 0 0       0 if ($face->can('face_name')) {
7585 0         0 my $face_name = $face->face_name();
7586 0 0       0 if ($face_name eq '') {
7587 0         0 $face_name = $params->{'face'};
7588 0         0 $face_name =~ s/\.(ttf|pfb)$//i;
7589             }
7590 0         0 return ($face_name);
7591             }
7592 0         0 return ($file);
7593             }
7594              
7595             sub load_image {
7596             =head2 load_image
7597              
7598             Loads an image at point x,y[,width,height]. To display it, pass it to blit_write.
7599              
7600             If you give centering options, the position to display the image is part of what is returned, and is ready for blitting.
7601              
7602             If 'width' and/or 'height' is given, the image is resized. Note, resizing is CPU intensive. Nevertheless, this is done by the Imager library (compiled C) so it is relatively fast.
7603              
7604             =over 4
7605              
7606             $fb->blit_write(
7607             $fb->load_image(
7608             {
7609             'x' => 0, # Optional (only applies if
7610             # CENTER_X or CENTER_XY is not
7611             # used)
7612              
7613             'y' => 0, # Optional (only applies if
7614             # CENTER_Y or CENTER_XY is not
7615             # used)
7616              
7617             'width' => 1920, # Optional. Resizes to this maximum
7618             # width. It fits the image to this
7619             # size.
7620              
7621             'height' => 1080, # Optional. Resizes to this maximum
7622             # height. It fits the image to this
7623             # size
7624              
7625             'scale_type' => 'min',# Optional. Sets the type of scaling
7626             #
7627             # 'min' = The smaller of the two
7628             # sizes are used (default)
7629             # 'max' = The larger of the two
7630             # sizes are used
7631             # 'nonprop' = Non-proportional sizing
7632             # The image is scaled to
7633             # width x height exactly.
7634              
7635             'autolevels' => FALSE,# Optional. It does a color
7636             # correction. Sometimes this
7637             # works well, and sometimes it
7638             # looks quite ugly. It depends
7639             # on the image
7640              
7641             'center' => CENTER_XY, # Optional
7642             # Three centering options are available
7643             # CENTER_X = center horizontally
7644             # CENTER_Y = center vertically
7645             # CENTER_XY = center horizontally and
7646             # vertically. Placing it
7647             # right in the middle of
7648             # the screen.
7649              
7650             'file' => 'RWBY_Faces.png', # Usually needs full path
7651              
7652             'convertalpha' => TRUE, # Converts the color matching the global
7653             # background color to have the same alpha
7654             # channel value as the global background,
7655             # which is beneficial for using 'merge'
7656             # in 'blit_transform'.
7657              
7658             'preserve_transparency' => FALSE,
7659             # Preserve the transparency of GIFs for
7660             # use with "mask_mode" playback.
7661             # This can allow for slightly faster
7662             # playback of animated GIFs on systems
7663             # using the acceration features of this
7664             # module. However, not all animated
7665             # GIFs look right when this is done.
7666             # the safest setting is to not use this,
7667             # and playback using normal_mode.
7668             }
7669             )
7670             );
7671              
7672             =back
7673              
7674             If a single image is loaded, it returns a reference to an anonymous hash, of the format:
7675              
7676             =over 4
7677              
7678             {
7679             'x' => horizontal position calculated (or passed through),
7680             'y' => vertical position calculated (or passed through),
7681             'width' => Width of the image,
7682             'height' => Height of the image,
7683             'tags' => The tags of the image (hashref)
7684             'image' => [raw image data]
7685             }
7686              
7687             =back
7688              
7689             If the image has multiple frames, then a reference to an array of hashes is returned:
7690              
7691             =over 4
7692              
7693             # NOTE: X and Y positions can change frame to frame, so use them for each frame!
7694             # Also, X and Y are based upon what was originally passed through, else they
7695             # reference 0,0 (but only if you didn't give an X,Y value initially).
7696              
7697             # ALSO: The tags may also specify offsets, and they will be taken into account.
7698              
7699             [
7700             { # Frame 1
7701             'x' => horizontal position calculated (or passed through),
7702             'y' => vertical position calculated (or passed through),
7703             'width' => Width of the image,
7704             'height' => Height of the image,
7705             'tags' => The tags of the image (hashref)
7706             'image' => [raw image data]
7707             },
7708             { # Frame 2 (and so on)
7709             'x' => horizontal position calculated (or passed through),
7710             'y' => vertical position calculated (or passed through),
7711             'width' => Width of the image,
7712             'height' => Height of the image,
7713             'tags' => The tags of the image (hashref)
7714             'image' => [raw image data]
7715             }
7716             ]
7717              
7718             =back
7719              
7720             =cut
7721              
7722 0     0 0 0 my $self = shift;
7723 0         0 my $params = shift;
7724              
7725 0         0 my @odata;
7726             my @Img;
7727 0         0 my ($x, $y, $xs, $ys, $w, $h, $last_img, $bench_scale, $bench_rotate, $bench_convert);
7728 0         0 my $bench_start = time;
7729 0         0 my $bench_total = $bench_start;
7730 0         0 my $bench_subtotal = $bench_start;
7731 0         0 my $bench_load = $bench_start;
7732 0         0 my $color_order = $self->{'COLOR_ORDER'};
7733 0 0       0 if ($params->{'file'} =~ /\.(gif|png|apng)$/i) {
7734 0         0 eval {
7735             @Img = Imager->read_multi(
7736             'file' => $params->{'file'},
7737             'allow_incomplete' => TRUE,
7738             'raw_datachannels' => max(3, $self->{'BYTES'}), # One of these is bound to work
7739 0         0 'datachannels' => max(3, $self->{'BYTES'}),
7740             );
7741             };
7742 0 0 0     0 warn __LINE__ . " $@" if ($@ && $self->{'SHOW_ERRORS'});
7743             } else {
7744 0         0 eval {
7745             push(@Img,Imager->new(
7746             'file' => $params->{'file'},
7747             'interleave' => FALSE,
7748             'allow_incomplete' => TRUE,
7749             'datachannels' => max(3, $self->{'BYTES'}), # One of these is bound to work.
7750 0         0 'raw_datachannels' => max(3, $self->{'BYTES'}),
7751             ));
7752             };
7753 0 0 0     0 warn __LINE__ . " $@" if ($@ && $self->{'SHOW_ERRORS'});
7754             }
7755 0         0 $bench_load = sprintf('%.03f', time - $bench_load);
7756 0 0       0 unless (defined($Img[0])) {
7757 0 0       0 warn __LINE__ . " I can't get Imager to set up an image buffer $params->{'file'}! Check your Imager installation.\n", Imager->errstr() if ($self->{'SHOW_ERRORS'});
7758             } else {
7759 0         0 foreach my $img (@Img) {
7760 0 0       0 next unless (defined($img));
7761 0         0 $bench_subtotal = time;
7762 0         0 my %tags = map(@$_, $img->tags());
7763             # Must loop and layer the frames on top of each other to get full frames.
7764 0 0       0 unless (exists($params->{'gif_left'})) {
7765 0 0       0 if (defined($last_img)) {
7766             $last_img->compose(
7767             'src' => $img,
7768             'tx' => $tags{'gif_left'},
7769 0         0 'ty' => $tags{'gif_top'},
7770             );
7771 0         0 $img = $last_img;
7772             }
7773 0 0       0 $last_img = $img->copy() unless (defined($last_img));
7774             }
7775 0         0 $bench_rotate = time;
7776 0 0       0 if (exists($tags{'exif_orientation'})) {
7777 0         0 my $orientation = $tags{'exif_orientation'};
7778 0 0 0     0 if (defined($orientation) && $orientation) { # Automatically rotate the image to correct
7779 0 0       0 if ($orientation == 3) { # 180 (It's upside down)
    0          
    0          
7780 0         0 $img = $img->rotate('degrees' => 180);
7781             } elsif ($orientation == 6) { # -90 (It's on its left side)
7782 0         0 $img = $img->rotate('degrees' => 90);
7783             } elsif ($orientation == 8) { # 90 (It's on its right size)
7784 0         0 $img = $img->rotate('degrees' => -90);
7785             }
7786             }
7787             }
7788 0         0 $bench_rotate = sprintf('%.03f', time - $bench_rotate);
7789              
7790             # Sometimes it works great, sometimes it looks uuuuuugly
7791 0 0       0 $img->filter('type' => 'autolevels') if ($params->{'autolevels'});
7792              
7793 0         0 $bench_scale = time;
7794 0         0 my %scale;
7795 0         0 $w = int($img->getwidth());
7796 0         0 $h = int($img->getheight());
7797 0         0 my $channels = $img->getchannels();
7798 0 0       0 if ($channels == 1) { # Monochrome
7799 0         0 $img = $img->convert('preset' => 'rgb');
7800 0         0 $channels = $img->getchannels();
7801             }
7802 0         0 my $bits = $img->bits();
7803              
7804             # Scale the image, if asked to
7805 0 0 0     0 if ($params->{'file'} =~ /\.(gif|png)$/i && ! exists($params->{'width'}) && ! exists($params->{'height'})) {
      0        
7806 0         0 ($params->{'width'}, $params->{'height'}) = ($w, $h);
7807             }
7808 0   0     0 $params->{'width'} = min($self->{'XRES'}, int($params->{'width'} || $w));
7809 0   0     0 $params->{'height'} = min($self->{'YRES'}, int($params->{'height'} || $h));
7810 0 0       0 if (defined($xs)) {
7811 0         0 $scale{'xscalefactor'} = $xs;
7812 0         0 $scale{'yscalefactor'} = $ys;
7813 0   0     0 $scale{'type'} = $params->{'scale_type'} || 'min';
7814 0         0 $img = $img->scale(%scale);
7815             } else {
7816 0         0 $scale{'xpixels'} = int($params->{'width'});
7817 0         0 $scale{'ypixels'} = int($params->{'height'});
7818 0   0     0 $scale{'type'} = $params->{'scale_type'} || 'min';
7819 0         0 ($xs, $ys, $w, $h) = $img->scale_calculate(%scale);
7820 0         0 $img = $img->scale(%scale);
7821             }
7822 0         0 $w = int($img->getwidth());
7823 0         0 $h = int($img->getheight());
7824 0         0 $bench_scale = sprintf('%.03f', time - $bench_scale);
7825 0         0 my $data = '';
7826 0         0 $bench_convert = time;
7827              
7828             # Remap colors
7829 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
7830 0         0 $img = $img->convert('matrix' => [[0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1]]);
7831             } elsif ($color_order == BRG) {
7832 0         0 $img = $img->convert('matrix' => [[0, 0, 1, 0], [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1]]);
7833             } elsif ($color_order == RBG) {
7834 0         0 $img = $img->convert('matrix' => [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]);
7835             } elsif ($color_order == GRB) {
7836 0         0 $img = $img->convert('matrix' => [[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]);
7837             } elsif ($color_order == GBR) {
7838 0         0 $img = $img->convert('matrix' => [[0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 0], [0, 0, 0, 1]]);
7839             }
7840 0 0       0 if ($self->{'BITS'} == 32) {
    0          
7841 0 0       0 $img = $img->convert('preset' => 'addalpha') if ($channels == 3);
7842 0         0 $img->write(
7843             'type' => 'raw',
7844             'interleave' => FALSE,
7845             'raw_datachannels' => 4,
7846             'raw_storechannels' => 4,
7847             'datachannels' => 4,
7848             'storechannels' => 4,
7849             'data' => \$data
7850             );
7851 0 0       0 if ($params->{'convertalpha'}) {
7852 0         0 my $oback = substr($self->{'RAW_BACKGROUND_COLOR'}, 0, 3);
7853 0         0 my $nback = $self->{'RAW_BACKGROUND_COLOR'};
7854 0         0 $data =~ s/$oback./$nback/g;
7855             }
7856             } elsif ($self->{'BITS'} == 24 ) {
7857 0 0       0 $img = $img->convert('preset' => 'noalpha') if ($channels == 4);
7858 0         0 $img->write(
7859             'type' => 'raw',
7860             'interleave' => FALSE,
7861             'raw_datachannels' => 3,
7862             'raw_storechannels' => 3,
7863             'datachannels' => 3,
7864             'storechannels' => 3,
7865             'data' => \$data
7866             );
7867             } else { # 16 bit
7868 0         0 $channels = $img->getchannels();
7869 0 0       0 $img = $img->convert('preset' => 'noalpha') if ($channels == 4);
7870 0         0 $img->write(
7871             'type' => 'raw',
7872             'interleave' => FALSE,
7873             'raw_datachannels' => 3,
7874             'raw_storechannels' => 3,
7875             'datachannels' => 3,
7876             'storechannels' => 3,
7877             'data' => \$data
7878             );
7879 0         0 $data = $self->_convert_24_to_16($data, RGB);
7880             }
7881              
7882 0 0 0     0 if (exists($params->{'center'})) { # Only accepted values are processed
    0          
7883 0 0       0 if ($params->{'center'} == CENTER_X) {
    0          
    0          
7884 0 0       0 $x = ($w < $self->{'W_CLIP'}) ? int(($self->{'W_CLIP'} - $w) / 2) + $self->{'X_CLIP'} : $self->{'X_CLIP'};
7885             } elsif ($params->{'center'} == CENTER_Y) {
7886 0 0       0 $y = ($h < $self->{'H_CLIP'}) ? int(($self->{'H_CLIP'} - $h) / 2) + $self->{'Y_CLIP'} : $self->{'Y_CLIP'};
7887             } elsif ($params->{'center'} == CENTER_XY) {
7888 0 0       0 $x = ($w < $self->{'W_CLIP'}) ? int(($self->{'W_CLIP'} - $w) / 2) + $self->{'X_CLIP'} : $self->{'X_CLIP'};
7889 0 0       0 $y = ($h < $self->{'H_CLIP'}) ? int(($self->{'H_CLIP'} - $h) / 2) + $self->{'Y_CLIP'} : $self->{'Y_CLIP'};
7890             }
7891             } elsif (defined($params->{'x'}) && defined($params->{'y'})) {
7892 0         0 $x = int($params->{'x'});
7893 0         0 $y = int($params->{'y'});
7894             } else {
7895 0 0       0 if ($w < $self->{'W_CLIP'}) {
    0          
7896 0         0 $x = int(($self->{'W_CLIP'} - $w) / 2) + $self->{'X_CLIP'};
7897 0         0 $y = 0;
7898             } elsif ($h < $self->{'H_CLIP'}) {
7899 0         0 $x = 0;
7900 0         0 $y = int(($self->{'H_CLIP'} - $h) / 2) + $self->{'Y_CLIP'};
7901             } else {
7902 0         0 $x = 0;
7903 0         0 $y = 0;
7904             }
7905             }
7906 0         0 $bench_convert = sprintf('%.03f', time - $bench_convert);
7907 0         0 $bench_total = sprintf('%.03f', time - $bench_start);
7908 0         0 $bench_subtotal = sprintf('%.03f', time - $bench_subtotal);
7909 0         0 my $temp_image = {
7910             'x' => $x,
7911             'y' => $y,
7912             'width' => $w,
7913             'height' => $h,
7914             'image' => $data,
7915             'tags' => \%tags,
7916             'benchmark' => {
7917             'load' => $bench_load,
7918             'rotate' => $bench_rotate,
7919             'scale' => $bench_scale,
7920             'convert' => $bench_convert,
7921             'sub-total' => $bench_subtotal,
7922             'total' => $bench_total
7923             }
7924             };
7925 0         0 push(@odata,$temp_image);
7926 0 0       0 if ($self->{'DIAGNOSTICS'}) {
7927 0         0 my $saved = $self->{'DRAW_MODE'};
7928 0 0       0 $self->mask_mode() if ($self->{'ACCELERATED'});
7929 0         0 $self->blit_write($odata[-1]);
7930 0         0 print STDERR "LOAD: $bench_load, ROTATE: $bench_rotate, SCALE: $bench_scale, CONVERT: $bench_convert, IMGTIME: $bench_subtotal, TOTAL: $bench_total \r";
7931 0         0 $self->{'DRAW_MODE'} = $saved;
7932             }
7933             }
7934              
7935 0 0       0 if (scalar(@odata) > 1) { # Animation
7936             return ( # return it in a form the blit routines can dig
7937             \@odata
7938 0         0 );
7939             } else { # Single image
7940             return ( # return it in a form the blit routines can dig
7941 0         0 pop(@odata)
7942             );
7943             }
7944             }
7945 0         0 return (undef); # Ouch
7946             }
7947              
7948             sub screen_dump {
7949             =head2 screen_dump
7950              
7951             Dumps the screen to a file given in 'file' in the format given in 'format'
7952              
7953             Formats can be (they are case-insensitive):
7954              
7955             =over 4
7956              
7957             =item B
7958              
7959             The most widely used format. This is a "lossy" format. The default quality setting is 75%, but it can be overriden with the "quality" parameter.
7960              
7961             =item B
7962              
7963             The CompuServe "Graphics Interchange Format". A very old and outdated format made specifically for VGA graphics modes, but still widely used. It only allows up to 256 "indexed" colors, so quality is very lacking. The "dither" paramter determines how colors are translated from 24 bit truecolor to 8 bit indexed.
7964              
7965             =item B
7966              
7967             The Portable Network Graphics format. Widely used, very high quality.
7968              
7969             =item B
7970              
7971             The Portable aNy Map format. These are typically "PPM" files. Not widely used.
7972              
7973             =item B
7974              
7975             The Targa image format. This is a high-color, lossless format, typically used in photography
7976              
7977             =item B
7978              
7979             The Tagged Image File Format. Sort of an older version of PNG (but not the same, just similar in capability). Sometimes used in FAX formats.
7980              
7981             =back
7982              
7983             $fb->screen_dump(
7984             {
7985             'file' => '/path/filename', # name of file to be written
7986             'format' => 'jpeg', # jpeg, gif, png, pnm, tga, or tiff
7987              
7988             # for JPEG formats only
7989             'quality' => 75, # quality of the JPEG file 1-100% (the
7990             # higher the number, the better the
7991             # quality, but the larger the file)
7992              
7993             # for GIF formats only
7994             'dither' => 'floyd', # Can be "floyd", "jarvis" or "stucki"
7995             }
7996             );
7997              
7998             =cut
7999              
8000 0     0 0 0 my $self = shift;
8001 0         0 my $params = shift;
8002              
8003 0   0     0 my $filename = $params->{'file'} || 'screendump.jpg';
8004 0         0 my $bytes = $self->{'BYTES'};
8005 0         0 my ($width, $height) = ($self->{'XRES'}, $self->{'YRES'});
8006 0         0 my $scrn = $self->blit_read({ 'x' => 0, 'y' => 0, 'width' => $width, 'height' => $height });
8007              
8008 0 0       0 $scrn->{'image'} = $self->_convert_16_to_24($scrn->{'image'}, $self->{'COLOR_MODE'}) if ($self->{'BITS'} == 16);
8009              
8010 0   0     0 my $type = lc($params->{'format'} || 'jpeg');
8011 0         0 $type =~ s/jpg/jpeg/;
8012 0         0 my $img = Imager::new();
8013             $img->read(
8014             'xsize' => $scrn->{'width'},
8015             'ysize' => $scrn->{'height'},
8016             'raw_datachannels' => max(3, $bytes),
8017             'raw_storechannels' => max(3, $bytes),
8018             'raw_interleave' => FALSE,
8019 0         0 'data' => $scrn->{'image'},
8020             'type' => 'raw',
8021             'allow_incomplete' => TRUE
8022             );
8023 0   0     0 my %p = (
8024             'type' => $type || 'raw',
8025             'datachannels' => max(3, $bytes),
8026             'storechannels' => max(3, $bytes),
8027             'interleave' => FALSE,
8028             'file' => $filename
8029             );
8030              
8031 0 0       0 if ($type eq 'jpeg') {
    0          
8032 0 0       0 $p{'jpegquality'} = $params->{'quality'} if (exists($params->{'quality'}));
8033 0         0 $p{'jpegoptimize'} = TRUE;
8034             } elsif ($type eq 'gif') {
8035 0         0 $p{'translate'} = 'errdiff';
8036 0   0     0 $p{'errdiff'} = lc($params->{'dither'} || 'floyd');
8037             }
8038 0         0 $img->write(%p);
8039             }
8040              
8041             ### Bitmap conversion routines ###
8042              
8043             sub _convert_16_to_24 {
8044             # Convert 16 bit bitmap to 24 bit bitmap
8045 0     0   0 my $self = shift;
8046 0         0 my $img = shift;
8047 0         0 my $color_order = shift;
8048              
8049 0         0 my $size = length($img);
8050 0         0 my $new_img = '';
8051 0 0       0 if ($self->{'ACCELERATED'}) {
8052 0         0 $new_img = chr(0) x (int(($size / 2) * 3) + 3);
8053 0         0 c_convert_16_24($img, $size, $new_img, $color_order);
8054             } else {
8055 0         0 my $black24 = chr(0) x 3;
8056 0         0 my $black16 = chr(0) x 2;
8057 0         0 my $white24 = chr(255) x 3;
8058 0         0 my $white16 = chr(255) x 2;
8059 0         0 my $idx = 0;
8060 0         0 while ($idx < $size) {
8061 0         0 my $color = substr($img, $idx, 2);
8062              
8063             # Black and white can be optimized
8064 0 0       0 if ($color eq $black16) {
    0          
8065 0         0 $new_img .= $black24;
8066             } elsif ($color eq $white16) {
8067 0         0 $new_img .= $white24;
8068             } else {
8069 0         0 $color = $self->RGB565_to_RGB888({ 'color' => $color, 'color_order' => $color_order });
8070 0         0 $new_img .= $color->{'color'};
8071             }
8072 0         0 $idx += 2;
8073             }
8074             }
8075 0         0 return ($new_img);
8076             }
8077              
8078             sub _convert_8_to_32 {
8079             # Convert 8 bit bitmap to 32 bit bitmap
8080 0     0   0 my $self = shift;
8081 0         0 my $img = shift;
8082 0         0 my $color_order = shift;
8083 0         0 my $pallette = shift; # Reference to an array of 256 pallette entries
8084              
8085 0         0 my $size = length($img);
8086 0         0 my $new_img = '';
8087 0         0 my $idx = 0;
8088 0         0 while ($idx < $size) {
8089 0         0 my $color = $self->RGB888_to_RGB8888({'color' => $pallette->[unpack('C',substr($img,$idx,1))]});
8090 0         0 $new_img .= $color->{'color'};
8091 0         0 $idx++;
8092             }
8093 0         0 return($new_img);
8094             }
8095              
8096             sub _convert_8_to_24 {
8097             # Convert 8 bit bitmap to 24 bit bitmap
8098 0     0   0 my $self = shift;
8099 0         0 my $img = shift;
8100 0         0 my $color_order = shift;
8101 0         0 my $pallette = shift; # Reference to an array of 256 pallette entries
8102              
8103 0         0 my $size = length($img);
8104 0         0 my $new_img = '';
8105 0         0 my $idx = 0;
8106 0         0 while ($idx < $size) {
8107 0         0 my $color = $pallette->[unpack('C',substr($img,$idx,1))];
8108 0         0 $new_img .= $color;
8109 0         0 $idx++;
8110             }
8111 0         0 return($new_img);
8112             }
8113              
8114             sub _convert_8_to_16 {
8115             # Convert 8 bit bitmap to 16 bit bitmap
8116 0     0   0 my $self = shift;
8117 0         0 my $img = shift;
8118 0         0 my $color_order = shift;
8119 0         0 my $pallette = shift; # Reference to an array of 256 pallette entries
8120              
8121 0         0 my $size = length($img);
8122 0         0 my $new_img = '';
8123 0         0 my $idx = 0;
8124 0         0 while ($idx < $size) {
8125 0         0 my $color = $self->RGB888_to_RGB565({'color' => $pallette->[unpack('C',substr($img,$idx,1))]});
8126 0         0 $new_img .= $color->{'color'};
8127 0         0 $idx++;
8128             }
8129 0         0 return($new_img);
8130             }
8131              
8132             sub _convert_16_to_32 {
8133             # Convert 16 bit bitmap to 32 bit bitmap
8134 0     0   0 my $self = shift;
8135 0         0 my $img = shift;
8136 0         0 my $color_order = shift;
8137              
8138 0         0 my $size = length($img);
8139 0         0 my $new_img = '';
8140 0 0       0 if ($self->{'ACCELERATED'}) {
8141 0         0 $new_img = chr(0) x (int($size * 2) + 4);
8142 0         0 c_convert_16_32($img, $size, $new_img, $color_order);
8143             } else {
8144 0         0 my $black32 = chr(0) x 4;
8145 0         0 my $black16 = chr(0) x 2;
8146 0         0 my $white32 = chr(255) x 4;
8147 0         0 my $white16 = chr(255) x 2;
8148 0         0 my $idx = 0;
8149 0         0 while ($idx < $size) {
8150 0         0 my $color = substr($img, $idx, 2);
8151              
8152             # Black and white can be optimized
8153 0 0       0 if ($color eq $black16) {
    0          
8154 0         0 $new_img .= $black32;
8155             } elsif ($color eq $white16) {
8156 0         0 $new_img .= $white32;
8157             } else {
8158 0         0 $color = $self->RGB565_to_RGBA8888({ 'color' => $color, 'color_order' => $color_order });
8159 0         0 $new_img .= $color->{'color'};
8160             }
8161 0         0 $idx += 2;
8162             }
8163             }
8164 0         0 return ($new_img);
8165             }
8166              
8167             sub _convert_24_to_16 {
8168             # Convert 24 bit bitmap to 16 bit bitmap
8169 0     0   0 my $self = shift;
8170 0         0 my $img = shift;
8171 0         0 my $color_order = shift;
8172              
8173 0         0 my $size = length($img);
8174 0         0 my $new_img = '';
8175 0 0       0 if ($self->{'ACCELERATED'}) {
8176 0         0 $new_img = chr(0) x (int(($size / 3) * 2) + 2);
8177 0         0 c_convert_24_16($img, $size, $new_img, $color_order);
8178             } else {
8179 0         0 my $black24 = chr(0) x 3;
8180 0         0 my $black16 = chr(0) x 2;
8181 0         0 my $white24 = chr(255) x 3;
8182 0         0 my $white16 = chr(255) x 2;
8183              
8184 0         0 my $idx = 0;
8185 0         0 while ($idx < $size) {
8186 0         0 my $color = substr($img, $idx, 3);
8187              
8188             # Black and white can be optimized
8189 0 0       0 if ($color eq $black24) {
    0          
8190 0         0 $new_img .= $black16;
8191             } elsif ($color eq $white24) {
8192 0         0 $new_img .= $white16;
8193             } else {
8194 0         0 $color = $self->RGB888_to_RGB565({ 'color' => $color, 'color_order' => $color_order });
8195 0         0 $new_img .= $color->{'color'};
8196             }
8197 0         0 $idx += 3;
8198             }
8199             }
8200 0         0 return ($new_img);
8201             }
8202              
8203             sub _convert_32_to_16 {
8204             # Convert 32 bit bitmap to a 16 bit bitmap
8205 0     0   0 my $self = shift;
8206 0         0 my $img = shift;
8207 0         0 my $color_order = shift;
8208              
8209 0         0 my $size = length($img);
8210 0         0 my $new_img = '';
8211 0 0       0 if ($self->{'ACCELERATED'}) {
8212 0         0 $new_img = chr(0) x (int($size / 2) + 2);
8213 0         0 c_convert_32_16($img, $size, $new_img, $color_order);
8214             } else {
8215 0         0 my $black32 = chr(0) x 4;
8216 0         0 my $black16 = chr(0) x 2;
8217 0         0 my $white32 = chr(255) x 4;
8218 0         0 my $white16 = chr(255) x 2;
8219              
8220 0         0 my $idx = 0;
8221 0         0 while ($idx < $size) {
8222 0         0 my $color = substr($img, $idx, 4);
8223              
8224             # Black and white can be optimized
8225 0 0       0 if ($color eq $black32) {
    0          
8226 0         0 $new_img .= $black16;
8227             } elsif ($color eq $white32) {
8228 0         0 $new_img .= $white16;
8229             } else {
8230 0         0 $color = $self->RGBA8888_to_RGB565({ 'color' => $color, 'color_order' => $color_order });
8231 0         0 $new_img .= $color->{'color'};
8232             }
8233 0         0 $idx += 4;
8234             }
8235             }
8236 0         0 return ($new_img);
8237             }
8238              
8239             sub _convert_32_to_24 {
8240             # Convert a 32 bit bitmap to a 24 bit bitmap.
8241 0     0   0 my $self = shift;
8242 0         0 my $img = shift;
8243 0         0 my $color_order = shift;
8244              
8245 0         0 my $size = length($img);
8246 0         0 my $new_img = '';
8247 0 0       0 if ($self->{'ACCELERATED'}) {
8248 0         0 $new_img = chr(0) x (int(($size / 4) * 3) + 3);
8249 0         0 c_convert_32_24($img, $size, $new_img, $color_order);
8250             } else {
8251 0         0 my $black32 = chr(0) x 4;
8252 0         0 my $black24 = chr(0) x 3;
8253 0         0 my $white32 = chr(255) x 4;
8254 0         0 my $white24 = chr(255) x 3;
8255              
8256 0         0 my $idx = 0;
8257 0         0 while ($idx < $size) {
8258 0         0 my $color = substr($img, $idx, 4);
8259              
8260             # Black and white can be optimized
8261 0 0       0 if ($color eq $black32) {
    0          
8262 0         0 $new_img .= $black24;
8263             } elsif ($color eq $white32) {
8264 0         0 $new_img .= $white24;
8265             } else {
8266 0         0 $color = $self->RGBA8888_to_RGB888({ 'color' => $color, 'color_order' => $color_order });
8267 0         0 $new_img .= $color->{'color'};
8268             }
8269 0         0 $idx += 4;
8270             }
8271             }
8272 0         0 return ($new_img);
8273             }
8274              
8275             sub _convert_24_to_32 {
8276             # Convert a 24 bit bitmap to a 32 bit bipmap
8277 0     0   0 my $self = shift;
8278 0         0 my $img = shift;
8279 0         0 my $color_order = shift;
8280              
8281 0         0 my $size = length($img);
8282 0         0 my $new_img = '';
8283 0 0       0 if ($self->{'ACCELERATED'}) {
8284 0         0 $new_img = chr(0) x (int(($size / 3) * 4) + 4);
8285 0         0 c_convert_24_32($img, $size, $new_img, $color_order);
8286             } else {
8287 0         0 my $black32 = chr(0) x 4;
8288 0         0 my $black24 = chr(0) x 3;
8289 0         0 my $white32 = chr(255) x 4;
8290 0         0 my $white24 = chr(255) x 3;
8291              
8292 0         0 my $idx = 0;
8293 0         0 while ($idx < $size) {
8294 0         0 my $color = substr($img, $idx, 4);
8295              
8296             # Black and white can be optimized
8297 0 0       0 if ($color eq $black24) {
    0          
8298 0         0 $new_img .= $black32;
8299             } elsif ($color eq $white24) {
8300 0         0 $new_img .= $white32;
8301             } else {
8302 0         0 $color = $self->RGB888_to_RGBA8888({ 'color' => $color, 'color_order' => $color_order });
8303 0         0 $new_img .= $color->{'color'};
8304             }
8305 0         0 $idx += 3;
8306             }
8307             }
8308 0         0 return ($new_img);
8309             }
8310              
8311             sub RGB565_to_RGB888 {
8312             =head2 RGB565_to_RGB888
8313              
8314             Convert a 16 bit color value to a 24 bit color value. This requires the color to be a two byte packed string.
8315              
8316             my $color24 = $fb->RGB565_to_RGB888(
8317             {
8318             'color' => $color16
8319             }
8320             );
8321              
8322             =cut
8323              
8324 0     0 0 0 my $self = shift;
8325 0         0 my $params = shift;
8326              
8327 0         0 my $rgb565 = unpack('S', $params->{'color'});
8328 0         0 my ($r, $g, $b);
8329 0         0 my $color_order = $params->{'color_order'};
8330 0 0       0 if ($color_order == RGB) {
    0          
    0          
    0          
    0          
    0          
8331 0         0 $r = $rgb565 & 31;
8332 0         0 $g = ($rgb565 >> 5) & 63;
8333 0         0 $b = ($rgb565 >> 11) & 31;
8334             } elsif ($color_order == BGR) {
8335 0         0 $b = $rgb565 & 31;
8336 0         0 $g = ($rgb565 >> 5) & 63;
8337 0         0 $r = ($rgb565 >> 11) & 31;
8338             } elsif ($color_order == BRG) {
8339 0         0 $b = $rgb565 & 31;
8340 0         0 $r = ($rgb565 >> 5) & 63;
8341 0         0 $g = ($rgb565 >> 11) & 31;
8342             } elsif ($color_order == RBG) {
8343 0         0 $r = $rgb565 & 31;
8344 0         0 $b = ($rgb565 >> 5) & 63;
8345 0         0 $g = ($rgb565 >> 11) & 31;
8346             } elsif ($color_order == GRB) {
8347 0         0 $g = $rgb565 & 31;
8348 0         0 $r = ($rgb565 >> 5) & 63;
8349 0         0 $b = ($rgb565 >> 11) & 31;
8350             } elsif ($color_order == GBR) {
8351 0         0 $g = $rgb565 & 31;
8352 0         0 $b = ($rgb565 >> 5) & 63;
8353 0         0 $r = ($rgb565 >> 11) & 31;
8354             }
8355 0         0 $r = int($r * 527 + 23) >> 6;
8356 0         0 $g = int($g * 259 + 33) >> 6;
8357 0         0 $b = int($b * 527 + 23) >> 6;
8358              
8359 0         0 my $color;
8360 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
8361 0         0 ($r, $g, $b) = ($b, $g, $r);
8362             } elsif ($color_order == BRG) {
8363 0         0 ($r, $g, $b) = ($b, $r, $g);
8364             # } elsif ($color_order == RGB) { # Redundant, but here for clarity
8365             } elsif ($color_order == RBG) {
8366 0         0 ($r, $g, $b) = ($r, $b, $g);
8367             } elsif ($color_order == GRB) {
8368 0         0 ($r, $g, $b) = ($g, $r, $b);
8369             } elsif ($color_order == GBR) {
8370 0         0 ($r, $g, $b) = ($g, $b, $r);
8371             }
8372 0         0 $color = pack('CCC', $r, $g, $b);
8373 0         0 return ({ 'color' => $color });
8374             }
8375              
8376             sub RGB565_to_RGBA8888 {
8377             =head2 RGB565_to_RGB8888
8378              
8379             Convert a 16 bit color value to a 32 bit color value. This requires the color to be a two byte packed string. The alpha value is either a value passed in or the default 255.
8380              
8381             my $color32 = $fb->RGB565_to_RGB8888(
8382             {
8383             'color' => $color16, # Required
8384             'alpha' => 128 # Optional
8385             }
8386             );
8387              
8388             =cut
8389              
8390 0     0 0 0 my $self = shift;
8391 0         0 my $params = shift;
8392              
8393 0         0 my $rgb565 = unpack('S', $params->{'color'});
8394 0   0     0 my $a = $params->{'alpha'} || 255;
8395 0         0 my $color_order = $self->{'COLOR_ORDER'};
8396 0         0 my ($r, $g, $b);
8397 0 0       0 if ($color_order == RGB) {
    0          
    0          
    0          
    0          
    0          
8398 0         0 $r = $rgb565 & 31;
8399 0         0 $g = ($rgb565 >> 5) & 63;
8400 0         0 $b = ($rgb565 >> 11) & 31;
8401             } elsif ($color_order == BGR) {
8402 0         0 $b = $rgb565 & 31;
8403 0         0 $g = ($rgb565 >> 5) & 63;
8404 0         0 $r = ($rgb565 >> 11) & 31;
8405             } elsif ($color_order == BRG) {
8406 0         0 $b = $rgb565 & 31;
8407 0         0 $r = ($rgb565 >> 5) & 63;
8408 0         0 $g = ($rgb565 >> 11) & 31;
8409             } elsif ($color_order == RBG) {
8410 0         0 $r = $rgb565 & 31;
8411 0         0 $b = ($rgb565 >> 5) & 63;
8412 0         0 $g = ($rgb565 >> 11) & 31;
8413             } elsif ($color_order == GRB) {
8414 0         0 $g = $rgb565 & 31;
8415 0         0 $r = ($rgb565 >> 5) & 63;
8416 0         0 $b = ($rgb565 >> 11) & 31;
8417             } elsif ($color_order == GBR) {
8418 0         0 $g = $rgb565 & 31;
8419 0         0 $b = ($rgb565 >> 5) & 63;
8420 0         0 $r = ($rgb565 >> 11) & 31;
8421             }
8422 0         0 $r = int($r * 527 + 23) >> 6;
8423 0         0 $g = int($g * 259 + 33) >> 6;
8424 0         0 $b = int($b * 527 + 23) >> 6;
8425              
8426 0         0 my $color;
8427 0 0       0 if ($color_order == BGR) {
    0          
    0          
    0          
    0          
8428 0         0 ($r, $g, $b) = ($b, $g, $r);
8429             # } elsif ($color_order == RGB) { # Redundant
8430             } elsif ($color_order == BRG) {
8431 0         0 ($r, $g, $b) = ($b, $r, $g);
8432             } elsif ($color_order == RBG) {
8433 0         0 ($r, $g, $b) = ($r, $b, $g);
8434             } elsif ($color_order == GRB) {
8435 0         0 ($r, $g, $b) = ($g, $r, $b);
8436             } elsif ($color_order == GBR) {
8437 0         0 ($r, $g, $b) = ($g, $b, $r);
8438             }
8439 0         0 $color = pack('CCCC', $r, $g, $b, $a);
8440 0         0 return ({ 'color' => $color });
8441             }
8442              
8443             sub RGB888_to_RGB565 {
8444             =head2 RGB888_to_RGB565
8445              
8446             Convert 24 bit color value to a 16 bit color value. This requires a three byte packed string.
8447              
8448             my $color16 = $fb->RGB888_to_RGB565(
8449             {
8450             'color' => $color24
8451             }
8452             );
8453              
8454             This simply does a bitshift, nothing more.
8455              
8456             =cut
8457              
8458 0     0 0 0 my $self = shift;
8459 0         0 my $params = shift;
8460              
8461 0         0 my $big_data = $params->{'color'};
8462 0 0       0 my $in_color_order = defined($params->{'color_order'}) ? $params->{'color_order'} : $self->{'COLOR_ORDER'};
8463 0         0 my $color_order = $self->{'COLOR_ORDER'};
8464              
8465 0         0 my $n_data;
8466 0 0       0 if ($big_data ne '') {
8467 0         0 my $pixel_data = substr($big_data, 0, 3);
8468 0         0 my ($r, $g, $b);
8469 0 0       0 if ($in_color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
8470 0         0 ($b, $g, $r) = unpack('C3', $pixel_data);
8471             } elsif ($in_color_order == RGB) {
8472 0         0 ($r, $g, $b) = unpack('C3', $pixel_data);
8473             } elsif ($in_color_order == BRG) {
8474 0         0 ($b, $r, $g) = unpack('C3', $pixel_data);
8475             } elsif ($in_color_order == RBG) {
8476 0         0 ($r, $b, $g) = unpack('C3', $pixel_data);
8477             } elsif ($in_color_order == GRB) {
8478 0         0 ($g, $r, $b) = unpack('C3', $pixel_data);
8479             } elsif ($in_color_order == GBR) {
8480 0         0 ($g, $b, $r) = unpack('C3', $pixel_data);
8481             }
8482 0         0 $r = $r >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'});
8483 0         0 $g = $g >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'});
8484 0         0 $b = $b >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});
8485             my $color =
8486             ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) |
8487             ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) |
8488 0         0 ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}));
8489 0         0 $n_data = pack('S', $color);
8490             }
8491 0         0 return ({ 'color' => $n_data });
8492             }
8493              
8494             sub RGBA8888_to_RGB565 {
8495             =head2 RGBA8888_to_RGB565
8496              
8497             Convert 32 bit color value to a 16 bit color value. This requires a four byte packed string.
8498              
8499             my $color16 = $fb->RGB8888_to_RGB565(
8500             {
8501             'color' => $color32,
8502             }
8503             );
8504              
8505             This simply does a bitshift, nothing more
8506              
8507             =cut
8508              
8509 0     0 0 0 my $self = shift;
8510 0         0 my $params = shift;
8511              
8512 0         0 my $big_data = $params->{'color'};
8513 0 0       0 my $in_color_order = defined($params->{'color_order'}) ? $params->{'color_order'} : $self->{'COLOR_ORDER'};
8514 0         0 my $color_order = $self->{'COLOR_ORDER'};
8515              
8516 0         0 my $n_data;
8517 0         0 while ($big_data ne '') {
8518 0         0 my $pixel_data = substr($big_data, 0, 4);
8519 0         0 $big_data = substr($big_data, 4);
8520 0         0 my ($r, $g, $b, $a);
8521 0 0       0 if ($in_color_order == BGR) {
    0          
    0          
    0          
    0          
    0          
8522 0         0 ($b, $g, $r, $a) = unpack('C4', $pixel_data);
8523             } elsif ($in_color_order == RGB) {
8524 0         0 ($r, $g, $b, $a) = unpack('C4', $pixel_data);
8525             } elsif ($in_color_order == BRG) {
8526 0         0 ($b, $r, $g, $a) = unpack('C4', $pixel_data);
8527             } elsif ($in_color_order == RBG) {
8528 0         0 ($r, $b, $g, $a) = unpack('C4', $pixel_data);
8529             } elsif ($in_color_order == GRB) {
8530 0         0 ($g, $r, $b, $a) = unpack('C4', $pixel_data);
8531             } elsif ($in_color_order == GBR) {
8532 0         0 ($g, $b, $r, $a) = unpack('C4', $pixel_data);
8533             }
8534              
8535             # Alpha is tossed
8536 0         0 $r = $r >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'});
8537 0         0 $g = $g >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'});
8538 0         0 $b = $b >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});
8539              
8540             my $color =
8541             ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) |
8542             ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) |
8543 0         0 ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}));
8544 0         0 $n_data .= pack('S', $color);
8545             }
8546 0         0 return ({ 'color' => $n_data });
8547             }
8548              
8549             sub RGB888_to_RGBA8888 {
8550             =head2 RGB888_to_RGBA8888
8551              
8552             Convert 24 bit color value to a 32 bit color value. This requires a three byte packed string. The alpha value is either a value passed in or the default 255.
8553              
8554             my $color32 = $fb->RGB888_to_RGBA8888(
8555             {
8556             'color' => $color24,
8557             'alpha' => 64
8558             }
8559             );
8560              
8561             This just simply adds an alpha value. No actual color conversion is done.
8562              
8563             =cut
8564              
8565 0     0 0 0 my $self = shift;
8566 0         0 my $params = shift;
8567              
8568 0 0       0 my $alpha = (exists($params->{'alpha'})) ? $params->{'alpha'} : 255;
8569 0         0 my $big_data = $params->{'color'};
8570 0         0 my $bsize = length($big_data);
8571 0         0 my $n_data = chr($alpha) x (($bsize / 3) * 4);
8572 0         0 my $index = 0;
8573 0         0 for (my $count = 0; $count < $bsize; $count += 3) {
8574 0         0 substr($n_data, $index, 3) = substr($big_data, $count + 2, 1) . substr($big_data, $count + 1, 1) . substr($big_data, $count, 1);
8575 0         0 $index += 4;
8576             }
8577 0         0 return ({ 'color' => $n_data });
8578             }
8579              
8580             sub RGBA8888_to_RGB888 {
8581             =head2 RGBA8888_to_RGB888
8582              
8583             Convert 32 bit color value to a 24 bit color value. This requires a four byte packed string.
8584              
8585             my $color24 = $fb->RGBA8888_to_RGB888(
8586             {
8587             'color' => $color32
8588             }
8589             );
8590              
8591             This just removes the alpha value. No color conversion is actually done.
8592              
8593             =cut
8594              
8595 0     0 0 0 my $self = shift;
8596 0         0 my $params = shift;
8597              
8598 0         0 my $big_data = $params->{'color'};
8599 0         0 my $bsize = length($big_data);
8600 0         0 my $n_data = chr(255) x (($bsize / 4) * 3);
8601 0         0 my $index = 0;
8602 0         0 for (my $count = 0; $count < $bsize; $count += 4) {
8603 0         0 substr($n_data, $index, 3) = substr($big_data, $count + 2, 1) . substr($big_data, $count + 1, 1) . substr($big_data, $count, 1);
8604 0         0 $index += 3;
8605             }
8606 0         0 return ({ 'color' => $n_data });
8607             }
8608              
8609             sub vsync {
8610             =head2 vsync
8611              
8612             Waits for vertical sync
8613              
8614             * Not all framebuffer drivers have this capability and ignore this call. Results may vary, as this cannot be emulated.
8615              
8616             Waits for the vertical blank before returning
8617              
8618             =cut
8619              
8620 0     0 0 0 my $self = shift;
8621 0         0 _set_ioctl(FBIO_WAITFORVSYNC, 'I', $self->{'FB'}, 0);
8622             }
8623              
8624             sub which_console {
8625             =head2 which_console
8626              
8627             Returns the active console and the expected console
8628              
8629             my ($active_console, $expected_console) = $fb->which_console();
8630              
8631             =cut
8632              
8633 0     0 0 0 my $self = shift;
8634 0         0 $self->{'THIS_CONSOLE'} = _slurp('/sys/class/tty/tty0/active');
8635 0         0 $self->{'THIS_CONSOLE'} =~ s/\D+//gs;
8636 0         0 $self->{'THIS_CONSOLE'} += 0; # Force numeric
8637 0         0 return ($self->{'THIS_CONSOLE'}, $self->{'CONSOLE'});
8638             }
8639              
8640             sub active_console {
8641             =head2 active_console
8642              
8643             Indicates if the current console is the expected console. It returns true or false.
8644              
8645             if ($self->active_console()) {
8646             # Do something
8647             }
8648              
8649             =cut
8650              
8651 0     0 0 0 my $self = shift;
8652 0         0 my ($current, $original) = $self->which_console();
8653 0 0       0 if ($current == $original) {
8654 0         0 return (TRUE);
8655             }
8656 0         0 return (FALSE);
8657             }
8658              
8659             sub wait_for_console {
8660             =head2 wait_for_console
8661              
8662             Blocks actions until the expected console is active. The expected console is determined at the time the module is initialized.
8663              
8664             Due to speed considerations, YOU must do use this to do blocking, if desired. If you expect to be changing active consoles, then you will need to use this. However, if you do not plan to do ever change consoles when running this module, then don't use this feature, as your results will be faster.
8665              
8666             If a TRUE or FALSE is passed to this, then you can enable or disable blocking for subsequent calls.
8667              
8668             =cut
8669              
8670 0     0 0 0 my $self = shift;
8671 0 0       0 if (scalar(@_)) {
8672 0 0       0 $self->{'WAIT_FOR_CONSOLE'} = (shift =~ /^(true|on|1|enable)$/i) ? TRUE : FALSE;
8673             } else {
8674 0   0     0 while ($self->{'WAIT_FOR_CONSOLE'} && !$self->active_console()) {
8675 0         0 sleep .1;
8676             }
8677             }
8678             }
8679              
8680             ## These are pulled in via the Mouse module
8681              
8682             =head2 initialize_mouse
8683              
8684             Turns on/off the mouse handler.
8685              
8686             Note: This uses Perl's "alarm" feature. If you want to use threads, then don't use this to turn on the mouse.
8687              
8688             # $fb->initialize_mouse(1); # Turn on the mouse handler
8689              
8690             or
8691              
8692             # $fb->initialize_mouse(0); # Turn off the mouse handler
8693              
8694             =head2 poll_mouse
8695              
8696             The mouse handler. The "initialize_mouse" routine sets this as the "alarm" routine to handle mouse events.
8697              
8698             An alarm handler just works, but can possibly block if used as ... an alarm handler.
8699              
8700             I suggest running it in a thread instead, using your own code.
8701              
8702             =head2 get_mouse
8703              
8704             Returns the mouse coordinates.
8705              
8706             Return as an array:
8707              
8708             # my ($mouseb, $mousex, $mousey) = $fb->get_mouse();
8709              
8710             Return as a hash reference:
8711              
8712             # my $mouse = $fb->get_mouse();
8713              
8714             Returns
8715              
8716             {
8717             'button' => button value, # Button state according to bits
8718             # Bit 0 = Left
8719             # Bit 1 = Right
8720             # Other bits according to driver
8721             'x' => Mouse X coordinate,
8722             'y' => Mouse Y coordinate,
8723             }
8724              
8725             =head2 set_mouse
8726              
8727             Sets the mouse position
8728              
8729             $fb->set_mouse(
8730             {
8731             'x' => 0,
8732             'y' => 0,
8733             }
8734             );
8735              
8736             =cut
8737              
8738             ##############################################################################
8739             ####################### NON-METHODS, FLAT SUBROUTINES ########################
8740             ##############################################################################
8741             sub _transformed_bounds {
8742 0     0   0 my $bbox = shift;
8743 0         0 my $matrix = shift;
8744              
8745 0         0 my $bounds;
8746 0         0 foreach my $point ([$bbox->start_offset, $bbox->ascent], [$bbox->start_offset, $bbox->descent], [$bbox->end_offset, $bbox->ascent], [$bbox->end_offset, $bbox->descent]) {
8747 0         0 $bounds = _add_bound($bounds, _transform_point(@{$point}, $matrix));
  0         0  
8748             }
8749 0         0 return (@{$bounds});
  0         0  
8750             }
8751              
8752             sub _add_bound {
8753 0     0   0 my $bounds = shift;
8754 0         0 my $x = shift;
8755 0         0 my $y = shift;
8756              
8757 0 0       0 $bounds or return ([$x, $y, $x, $y]);
8758              
8759 0 0       0 $x < $bounds->[0] and $bounds->[0] = $x;
8760 0 0       0 $y < $bounds->[1] and $bounds->[1] = $y;
8761 0 0       0 $x > $bounds->[2] and $bounds->[2] = $x;
8762 0 0       0 $y > $bounds->[3] and $bounds->[3] = $y;
8763              
8764 0         0 return ($bounds);
8765             }
8766              
8767             sub _transform_point {
8768 0     0   0 my $x = shift;
8769 0         0 my $y = shift;
8770 0         0 my $matrix = shift;
8771              
8772 0         0 return ($x * $matrix->[0] + $y * $matrix->[1] + $matrix->[2], $x * $matrix->[3] + $y * $matrix->[4] + $matrix->[5]);
8773             }
8774              
8775             sub _get_ioctl {
8776             ##########################################################
8777             ## GET IOCTL INFO ##
8778             ##########################################################
8779             # 'sys/ioctl.ph' is flakey. Not used at the moment. #
8780             ##########################################################
8781             # Used to return an array specific to the ioctl function #
8782             ##########################################################
8783              
8784             # This really needs to be moved over to the C routines, as the structure really is hard to parse for different processor long types
8785             # ... aaaaand ... I did
8786 0     0   0 my $command = shift;
8787 0         0 my $format = shift;
8788 0         0 my $fb = shift;
8789 0         0 my $data = '';
8790 0         0 my @array;
8791 0         0 eval {
8792 0 0       0 if (defined($fb)) {
8793 0         0 ioctl($fb, $command, $data);
8794             } else {
8795 0         0 ioctl(STDOUT, $command, $data);
8796             }
8797             };
8798 0         0 @array = unpack($format, $data);
8799 0         0 return (@array);
8800             }
8801              
8802             sub _set_ioctl {
8803             ##########################################################
8804             ## SET IOCTL INFO ##
8805             ##########################################################
8806             # Used to call or set ioctl specific functions #
8807             ##########################################################
8808 0     0   0 my $command = shift;
8809 0         0 my $format = shift;
8810 0         0 my $fb = shift;
8811 0         0 my @array = @_;
8812              
8813 0         0 my $data = pack($format, @array);
8814 0         0 eval { return (ioctl($fb, $command, $data)); };
  0         0  
8815             }
8816              
8817             sub _slurp { # Just used for /proc
8818 2     2   8 my $file = shift;
8819 2         7 my $buffer = '';
8820 2         6 eval {
8821 2         282 open(my $sl,'<',$file);
8822 2         94 read($sl,$buffer,10);
8823 2         35 close($sl);
8824 2         28 $buffer = chomp($buffer);
8825             };
8826 2         22 return($buffer);
8827             }
8828              
8829             1;
8830              
8831             __END__