summaryrefslogtreecommitdiff
path: root/gs/base/gdevbmpa.c
blob: 48ad56bdc475f4c32079864627f7b3372b44f681 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
/* Copyright (C) 2001-2006 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/
   or contact Artifex Software, Inc.,  7 Mt. Lassen Drive - Suite A-134,
   San Rafael, CA  94903, U.S.A., +1(415)492-9861, for further information.
*/
/* $Id$ */
/* .BMP file format output drivers: Demo of ASYNC rendering */

/* 2000-04-20 ghost@aladdin.com - Makes device structures const, changing
   makefile entry from DEV to DEV2. */
/* 1998/12/29 ghost@aladdin.com - Modified to use gdev_prn_render_lines,
   which replaces the former "overlay" calls */
/* 1998/11/23 ghost@aladdin.com - Removed pointless restriction to
   single-page output */
/* 1998/7/28 ghost@aladdin.com - Factored out common BMP format code
   to gdevbmpc.c */
/* Initial version 2/2/98 by John Desrosiers (soho@crl.com) */

#include "stdio_.h"
#include "gserrors.h"
#include "gdevprna.h"
#include "gdevpccm.h"
#include "gdevbmp.h"
#include "gdevppla.h"
#include "gpsync.h"

/*
 * The original version of this driver was restricted to producing a single
 * page per file.  If for some reason you want to reinstate this
 * restriction, uncomment the next line.
 * NOTE: Even though the logic for multi-page files is straightforward,
 * it results in a file that most programs that process BMP format cannot
 * handle. Most programs will only display the first page.
 */
/*************** #define SINGLE_PAGE ****************/

/* ------ The device descriptors ------ */

/* Define data type for this device based on prn_device */
typedef struct gx_device_async_s {
    gx_device_common;
    gx_prn_device_common;
    bool UsePlanarBuffer;
    int buffered_page_exists;
    long file_offset_to_data[4];
} gx_device_async;

/* Define initializer for device */
#define async_device(procs, dname, w10, h10, xdpi, ydpi, lm, bm, rm, tm, color_bits, print_page)\
{ prn_device_std_margins_body(gx_device_async, procs, dname,\
    w10, h10, xdpi, ydpi, lm, tm, lm, bm, rm, tm, color_bits, print_page),\
    0, 0, { 0, 0, 0, 0 }\
}

static dev_proc_open_device(bmpa_writer_open);
static dev_proc_open_device(bmpa_cmyk_writer_open);
static prn_dev_proc_open_render_device(bmpa_reader_open_render_device);
static dev_proc_print_page_copies(bmpa_reader_print_page_copies);
/* VMS limits procedure names to 31 characters. */
static dev_proc_print_page_copies(bmpa_cmyk_reader_print_copies);
static prn_dev_proc_buffer_page(bmpa_reader_buffer_page);
static prn_dev_proc_buffer_page(bmpa_cmyk_reader_buffer_page);
static dev_proc_output_page(bmpa_reader_output_page);
static dev_proc_get_params(bmpa_get_params);
static dev_proc_put_params(bmpa_put_params);
static dev_proc_get_hardware_params(bmpa_get_hardware_params);
static prn_dev_proc_start_render_thread(bmpa_reader_start_render_thread);
static prn_dev_proc_get_space_params(bmpa_get_space_params);
#define default_print_page 0	/* not needed becoz print_page_copies def'd */

/* Monochrome. */

static const gx_device_procs bmpamono_procs =
  prn_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close);
const gx_device_async gs_bmpamono_device =
  async_device(bmpamono_procs, "bmpamono",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        X_DPI, Y_DPI,
        0,0,0,0,			/* margins */
        1, default_print_page);

/* 1-bit-per-plane separated CMYK color. */

#define bmpa_cmyk_procs(p_open, p_map_color_rgb, p_map_cmyk_color)\
    p_open, NULL, NULL, gdev_prn_output_page, gdev_prn_close,\
    NULL, p_map_color_rgb, NULL, NULL, NULL, NULL, NULL, NULL,\
    bmpa_get_params, bmpa_put_params,\
    p_map_cmyk_color, NULL, NULL, NULL, gx_page_device_get_page_device

static const gx_device_procs bmpasep1_procs = {
    bmpa_cmyk_procs(bmpa_cmyk_writer_open, cmyk_1bit_map_color_rgb,
                    cmyk_1bit_map_cmyk_color)
};
const gx_device_async gs_bmpasep1_device = {
  prn_device_body(gx_device_async, bmpasep1_procs, "bmpasep1",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        X_DPI, Y_DPI,
        0,0,0,0,			/* margins */
        4, 4, 1, 1, 2, 2, default_print_page)
};

/* 8-bit-per-plane separated CMYK color. */

static const gx_device_procs bmpasep8_procs = {
    bmpa_cmyk_procs(bmpa_cmyk_writer_open, cmyk_8bit_map_color_rgb,
                    cmyk_8bit_map_cmyk_color)
};
const gx_device_async gs_bmpasep8_device = {
  prn_device_body(gx_device_async, bmpasep8_procs, "bmpasep8",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        X_DPI, Y_DPI,
        0,0,0,0,			/* margins */
        4, 32, 255, 255, 256, 256, default_print_page)
};

/* 4-bit (EGA/VGA-style) color. */

static const gx_device_procs bmpa16_procs =
  prn_color_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close,
    pc_4bit_map_rgb_color, pc_4bit_map_color_rgb);
const gx_device_async gs_bmpa16_device =
  async_device(bmpa16_procs, "bmpa16",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        X_DPI, Y_DPI,
        0,0,0,0,			/* margins */
        4, default_print_page);

/* 8-bit (SuperVGA-style) color. */
/* (Uses a fixed palette of 3,3,2 bits.) */

static const gx_device_procs bmpa256_procs =
  prn_color_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close,
    pc_8bit_map_rgb_color, pc_8bit_map_color_rgb);
const gx_device_async gs_bmpa256_device =
  async_device(bmpa256_procs, "bmpa256",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        X_DPI, Y_DPI,
        0,0,0,0,			/* margins */
        8, default_print_page);

/* 24-bit color. */

static const gx_device_procs bmpa16m_procs =
  prn_color_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close,
    bmp_map_16m_rgb_color, bmp_map_16m_color_rgb);
const gx_device_async gs_bmpa16m_device =
  async_device(bmpa16m_procs, "bmpa16m",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        X_DPI, Y_DPI,
        0,0,0,0,			/* margins */
        24, default_print_page);

/* 32-bit CMYK color (outside the BMP specification). */

static const gx_device_procs bmpa32b_procs = {
    bmpa_cmyk_procs(bmpa_writer_open, gx_default_map_color_rgb,
                    gx_default_cmyk_map_cmyk_color)
};
const gx_device_async gs_bmpa32b_device =
  async_device(bmpa32b_procs, "bmpa32b",
               DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
               X_DPI, Y_DPI,
               0, 0, 0, 0,		/* margins */
               32, default_print_page);

/* --------- Forward declarations ---------- */

static void bmpa_reader_thread(void *);

/* ------------ Writer Instance procedures ---------- */

/* Writer's open procedure */
static int
bmpa_open_writer(gx_device *pdev  /* Driver instance to open */,
                 dev_proc_print_page_copies((*reader_print_page_copies)),
                 prn_dev_proc_buffer_page((*reader_buffer_page)))
{
    gx_device_async * const pwdev = (gx_device_async *)pdev;
    int max_width;
    int max_raster;
    int min_band_height;
    int max_src_image_row;

    /*
     * Set up device's printer proc vector to point to this driver, since
     * there are no convenient macros for setting them up in static template.
     */
    init_async_render_procs(pwdev, bmpa_reader_start_render_thread,
                            reader_buffer_page,
                            reader_print_page_copies);
    set_dev_proc(pdev, get_params, bmpa_get_params);	/* because not all device-init macros allow this to be defined */
    set_dev_proc(pdev, put_params, bmpa_put_params);	/* ibid. */
    set_dev_proc(pdev, get_hardware_params, bmpa_get_hardware_params);
    set_dev_proc(pdev, output_page, bmpa_reader_output_page);	/* hack */
    pwdev->printer_procs.get_space_params = bmpa_get_space_params;
    pwdev->printer_procs.open_render_device =
        bmpa_reader_open_render_device;	/* Included for tutorial value */

    /*
     * Determine MAXIMUM parameters this device will have to support over
     * lifetime.  See comments for bmpa_get_space_params().
     */
    max_width = DEFAULT_WIDTH_10THS * 60;   /* figure max wid = default @ 600dpi */
    min_band_height = max(1, (DEFAULT_HEIGHT_10THS * 60) / 100);
    max_raster = bitmap_raster(max_width * pwdev->color_info.depth);	/* doesn't need to be super accurate */
    max_src_image_row = max_width * 4 * 2;

    /* Set to planar buffering mode if appropriate. */
    if (pwdev->UsePlanarBuffer)
        gdev_prn_set_procs_planar(pdev);

    /* Special writer open routine for async interpretation */
    /* Starts render thread */
    return gdev_prn_async_write_open((gx_device_printer *)pdev,
                                     max_raster, min_band_height,
                                     max_src_image_row);
}
static int
bmpa_writer_open(gx_device *pdev  /* Driver instance to open */)
{
    return bmpa_open_writer(pdev, bmpa_reader_print_page_copies,
                            bmpa_reader_buffer_page);
}
static int
bmpa_cmyk_writer_open(gx_device *pdev  /* Driver instance to open */)
{
    return bmpa_open_writer(pdev, bmpa_cmyk_reader_print_copies,
                            bmpa_cmyk_reader_buffer_page);
}

/* -------------- Renderer instance procedures ----------*/

/* Forward declarations */
static int
    bmpa_reader_buffer_planes(gx_device_printer *pdev, FILE *prn_stream,
                              int num_copies, int first_plane,
                              int last_plane, int raster);

/* Thread to do rendering, started by bmpa_reader_start_render_thread */
static void
bmpa_reader_thread(void *params)
{
    gdev_prn_async_render_thread((gdev_prn_start_render_params *)params);
}

static int	/* rets 0 ok, -ve error if couldn't start thread */
bmpa_reader_start_render_thread(gdev_prn_start_render_params *params)
{
    return gp_create_thread(bmpa_reader_thread, params);
}

static int
bmpa_reader_open_render_device(gx_device_printer *ppdev)
{
    /*
     * Do anything that needs to be done at open time here.
     * Since this implementation doesn't do anything, we don't need to
     * cast the device argument to the more specific type.
     */
    /*gx_device_async * const prdev = (gx_device_async *)ppdev;*/

    /* Cascade down to the default handler */
    return gdev_prn_async_render_open(ppdev);
}

/* Generic routine to send the page to the printer. */
static int
bmpa_reader_output_page(gx_device *pdev, int num_copies, int flush)
{
    /*
     * HACK: open the printer page with the positionable attribute since
     * we need to seek back & forth to support partial rendering.
     */
    if ( num_copies > 0 || !flush ) {
        int code = gdev_prn_open_printer_positionable(pdev, 1, 1);

        if ( code < 0 )
            return code;
    }
    return gdev_prn_output_page(pdev, num_copies, flush);
}

static int
bmpa_reader_print_planes(gx_device_printer *pdev, FILE *prn_stream,
                         int num_copies, int first_plane, int last_plane,
                         int raster)
{
    gx_device_async * const prdev = (gx_device_async *)pdev;
    /* BMP scan lines are padded to 32 bits. */
    uint bmp_raster = raster + (-raster & 3);
    int code = 0;
    int y;
    byte *row = 0;
    byte *raster_data;
    int plane;

    /* If there's data in buffer, need to process w/overlays */
    if (prdev->buffered_page_exists) {
        code = bmpa_reader_buffer_planes(pdev, prn_stream, num_copies,
                                         first_plane, last_plane, raster);
        goto done;
    }
#ifdef SINGLE_PAGE
    /* BMP format is single page, so discard all but 1st printable page */
    /* Since the OutputFile may have a %d, we use ftell to determine if */
    /* this is a zero length file, which is legal to write		*/
    if (ftell(prn_stream) != 0)
        return 0;
#endif
    row = gs_alloc_bytes(pdev->memory, bmp_raster, "bmp file buffer");
    if (row == 0)		/* can't allocate row buffer */
        return_error(gs_error_VMerror);

    for (plane = first_plane; plane <= last_plane; ++plane) {
        gx_render_plane_t render_plane;

        /* Write header & seek to its end */
        code =
            (first_plane < 0 ? write_bmp_header(pdev, prn_stream) :
             write_bmp_separated_header(pdev, prn_stream));
        if (code < 0)
            goto done;
        /* Save the file offset where data begins */
        if ((prdev->file_offset_to_data[plane - first_plane] =
             ftell(prn_stream)) == -1L) {
            code = gs_note_error(gs_error_ioerror);
            goto done;
        }

        /*
         * Write out the bands top to bottom.  Finish the job even if
         * num_copies == 0, to avoid invalid output file.
         */
        if (plane >= 0)
            gx_render_plane_init(&render_plane, (gx_device *)pdev, plane);
        for (y = prdev->height - 1; y >= 0; y--) {
            uint actual_raster;

            code = gdev_prn_get_lines(pdev, y, 1, row, bmp_raster,
                                      &raster_data, &actual_raster,
                                      (plane < 0 ? NULL : &render_plane));
            if (code < 0)
                goto done;
            if (fwrite((const char *)raster_data, actual_raster, 1, prn_stream) < 1) {
                code = gs_error_ioerror;
                goto done;
            }
        }
    }
done:
    gs_free_object(pdev->memory, row, "bmp file buffer");
    prdev->buffered_page_exists = 0;
    return code;
}
static int
bmpa_reader_print_page_copies(gx_device_printer *pdev, FILE *prn_stream,
                              int num_copies)
{
    return bmpa_reader_print_planes(pdev, prn_stream, num_copies, -1, -1,
                                    gdev_prn_raster(pdev));
}
static int
bmpa_cmyk_plane_raster(gx_device_printer *pdev)
{
    return bitmap_raster(pdev->width * (pdev->color_info.depth / 4));
}
static int
bmpa_cmyk_reader_print_copies(gx_device_printer *pdev, FILE *prn_stream,
                              int num_copies)
{
    return bmpa_reader_print_planes(pdev, prn_stream, num_copies, 0, 3,
                                    bmpa_cmyk_plane_raster(pdev));
}

/* Buffer a (partial) rasterized page & optionally print result multiple times. */
static int
bmpa_reader_buffer_planes(gx_device_printer *pdev, FILE *file, int num_copies,
                          int first_plane, int last_plane, int raster)
{
    gx_device_async * const prdev = (gx_device_async *)pdev;
    gx_device * const dev = (gx_device *)pdev;
    int code = 0;

    /* If there's no data in buffer, no need to do any overlays */
    if (!prdev->buffered_page_exists) {
        code = bmpa_reader_print_planes(pdev, file, num_copies,
                                        first_plane, last_plane, raster);
        goto done;
    }

    /*
     * Continue rendering on top of the existing file. This requires setting
     * up a buffer of the existing bits in GS's format (except for optional
     * extra padding bytes at the end of each scan line, provided the scan
     * lines are still correctly memory-aligned) and then calling
     * gdev_prn_render_lines.  If the device already provides a band buffer
     * -- which currently is always the case -- we can use it if we want;
     * but if a device stores partially rendered pages in memory in a
     * compatible format (e.g., a printer with a hardware page buffer), it
     * can render directly on top of the stored bits.
     *
     * If we can render exactly one band (or N bands) at a time, this is
     * more efficient, since otherwise (a) band(s) will have to be rendered
     * more than once.
     */

    {
        byte *raster_data;
        gx_device_clist_reader *const crdev =
            (gx_device_clist_reader *)pdev;
        int raster = gx_device_raster(dev, 1);
        int padding = -raster & 3; /* BMP scan lines are padded to 32 bits. */
        int bmp_raster = raster + padding;
        int plane;

        /*
         * Get the address of the renderer's band buffer.  In the future,
         * it will be possible to suppress the allocation of this buffer,
         * and to use only buffers provided the driver itself (e.g., a
         * hardware buffer).
         */
        if (!pdev->buffer_space) {
            /* Not banding.  Can't happen. */
            code = gs_note_error(gs_error_Fatal);
            goto done;
        }
        raster_data = crdev->data;

        for (plane = first_plane; plane <= last_plane; ++plane) {
            gx_render_plane_t render_plane;
            gx_device *bdev;
            int y, band_base_line;

            /* Seek to beginning of data portion of file */
            if (fseek(file, prdev->file_offset_to_data[plane - first_plane],
                      SEEK_SET)) {
                code = gs_note_error(gs_error_ioerror);
                goto done;
            }

            if (plane >= 0)
                gx_render_plane_init(&render_plane, (gx_device *)pdev, plane);
            else
                render_plane.index = -1;

            /* Set up the buffer device. */
            code = gdev_create_buf_device(crdev->buf_procs.create_buf_device,
                                          &bdev, crdev->target, 0, &render_plane,
                                          dev->memory, NULL);
            if (code < 0)
                goto done;

            /*
             * Iterate thru bands from top to bottom.  As noted above, we
             * do this an entire band at a time for efficiency.
             */
            for (y = dev->height - 1; y >= 0; y = band_base_line - 1) {
                int band_height =
                    dev_proc(dev, get_band)(dev, y, &band_base_line);
                int line;
                gs_int_rect band_rect;

                /* Set up the buffer device for this band. */
                code = crdev->buf_procs.setup_buf_device
                    (bdev, raster_data, bmp_raster, NULL, 0, band_height,
                     band_height);
                if (code < 0)
                    goto done;

                /* Fill in the buffer with a band from the BMP file. */
                /* Need to do this backward since BMP is top to bottom. */
                for (line = band_height - 1; line >= 0; --line)
                    if (fread(raster_data + line * bmp_raster,
                              raster, 1, file) < 1 ||
                        fseek(file, padding, SEEK_CUR)
                        ) {
                        code = gs_note_error(gs_error_ioerror);
                        goto done;
                    }

                /* Continue rendering on top of the existing bits. */
                band_rect.p.x = 0;
                band_rect.p.y = band_base_line;
                band_rect.q.x = pdev->width;
                band_rect.q.y = band_base_line + band_height;
                if ((code = clist_render_rectangle((gx_device_clist *)pdev,
                                                   &band_rect, bdev,
                                                   &render_plane, false)) < 0)
                    goto done;

                /* Rewind & write out the updated buffer. */
                if (fseek(file, -bmp_raster * band_height, SEEK_CUR)) {
                    code = gs_note_error(gs_error_ioerror);
                    goto done;
                }
                for (line = band_height - 1; line >= 0; --line) {
                    if (fwrite(raster_data + line * bmp_raster,
                               bmp_raster, 1, file) < 1 ||
                        fseek(file, padding, SEEK_CUR)
                        ) {
                        code = gs_note_error(gs_error_ioerror);
                        goto done;
                    }
                }
            }
            crdev->buf_procs.destroy_buf_device(bdev);
        }
    }

 done:
    prdev->buffered_page_exists = (code >= 0);
    return code;
}
static int
bmpa_reader_buffer_page(gx_device_printer *pdev, FILE *prn_stream,
                        int num_copies)
{
    return bmpa_reader_buffer_planes(pdev, prn_stream, num_copies, -1, -1,
                                     gdev_prn_raster(pdev));
}
static int
bmpa_cmyk_reader_buffer_page(gx_device_printer *pdev, FILE *prn_stream,
                             int num_copies)
{
    return bmpa_reader_buffer_planes(pdev, prn_stream, num_copies, 0, 3,
                                     bmpa_cmyk_plane_raster(pdev));
}

/*------------ Procedures common to writer & renderer -------- */

/* Compute space parameters */
static void
bmpa_get_space_params(const gx_device_printer *pdev,
 gdev_prn_space_params *space_params)
{
    /* Plug params into device before opening it
     *
     * You ask "How did you come up with these #'s?" You asked, so...
     *
     * To answer clearly, let me begin by recapitulating how command list
     * (clist) device memory allocation works in the non-overlapped case:
     * When the device is opened, a buffer is allocated. How big? For
     * starters, it must be >= PRN_MIN_BUFFER_SPACE, and as we'll see, must
     * be sufficient to satisfy the rest of the band params. If you don't
     * specify a size for it in space_params.band.BandBufferSpace, the open
     * routine will use a heuristic where it tries to use PRN_BUFFER_SPACE,
     * then works its way down by factors of 2 if that much memory isn't
     * available.
     *
     * The device proceeds to divide the buffer into several parts: one of
     * them is used for the same thing during writing & rasterizing; the
     * other parts are redivided and used differently writing and
     * rasterizing. The limiting factor dictating memory requirements is the
     * rasterizer's render buffer.  This buffer needs to be able to contain
     * a pixmap that covers an entire band. Memory consumption is whatever
     * is needed to hold N rows of data aligned on word boundaries, +
     * sizeof(pointer) for each of N rows. Whatever is left over in the
     * rasterized is allocated to a tile cache. You want to make sure that
     * cache is at least 50KB.
     *
     * For example, take a 600 dpi b/w device at 8.5 x 11 inches.  For the
     * whole device, that's 6600 rows @ 638 bytes = ~4.2 MB total.  If the
     * device is divided into 100 bands, each band's rasterizer buffer is
     * 62K. Add on a 50K tile cache, and you get a 112KB (+ add a little
     * slop) total device buffer size.
     *
     * Now that we've covered the rasterizer, let's switch back to the
     * writer. The writer must have a tile cache *exactly* the same size as
     * the reader. This means that the space to divide up for the writer is
     * equal is size to the rasterizer's band buffer.  This space is divided
     * into 2 sections: per-band bookeeping info and a command buffer. The
     * bookeeping info currently uses ~72 bytes for each band. The rest is
     * the command buffer.
     *
     * To continue the same 112KB example, we have 62KB to slice up.
     * We need 72 bytes * 100 bands = 7.2KB, leaving a 55K command buffer.
     *
     * A larger command buffer has some performance (see gxclmem.c comments)
     * advantages in the general case, but is critical in one special case:
     * high-level images. Whenever possible, images are transmitted across
     * the band buffer in their original resolution and bits/pixel. The
     * alternative fallback behavior can be very slow.  Here, the relevant
     * restriction is that at least one entire source image row must fit
     * into the command buffer. This means that, in our example, an RGB
     * source image would have to be <= 18K pixels wide. If the image is
     * sampled at the same resolution as the hardware (600 dpi), that means
     * the row would be limited to a very reasonable 30 inches. However, if
     * the source image is sampled at 2400 dpi, that limit is only 7.5
     * inches. The situation gets worse as bands get smaller, but the
     * implementor must decide on the tradeoff point.
     *
     * The moral of the story is that you should never make a band
     * so small that its buffer limits the command buffer excessively.
     * Again, Max image row bytes = band buffer size - # bands * 72.
     *
     * In the overlapped case, everything is exactly as above, except that
     * two identical devices, each with an identical buffer, are allocated:
     * one for the writer, and one for the rasterizer. Because it's critical
     * to allocate identical buffers, I *strongly* recommend setting these
     * params in the writer's open routine:
     * space_params.band.BandBufferSpace, .BandWidth and .BandHeight.  If
     * you don't force these values to a known value, the memory allocation
     * heuristic may not come to the same result for both copies of the
     * device, since the first allocation will diminish the amount of free
     * memory.
     *
     * There is room for an important optimization here: allocate the
     * writer's space with enough memory for a generous command buffer, but
     * allocate the reader with only enough memory for a band rasterization
     * buffer and the tile cache.  To do this, observe that the space_params
     * struct has two sizes: BufferSpace vs. BandBufferSpace.  To start,
     * BandBufferSpace is always <= BufferSpace. On the reader side,
     * BandBufferSpace is divided between the tile cache and the rendering
     * buffer -- that's all the memory that's needed to rasterize. On the
     * writer's side, BandBufferSpace is divided the same way: the tile
     * cache (which must be identical to the reader's) is carved out, and
     * the space that would have been used for a rasterizing buffer is used
     * as a command buffer. However, you can further increase the cmd buf
     * further by setting BufferSize (not BandBufferSize) to a higher number
     * than BandBufferSize. In that case, the command buffer is increased by
     * the difference (BufferSize - BandBufferSize). There is logic in the
     * memory allocation for printers that will automatically use BufferSize
     * for writers (or non-async printers), and BandBufferSize for readers.
     *
     * Note: per the comments in gxclmem.c, the banding logic will perform
     * better with 1MB or better for the command list.
     */

    /* This will give us a very "ungenerous" buffer. */
    /* Here, my arbitrary rule for min image row is: twice the dest width */
    /* in full CMYK. */
    ulong render_space = 0;
    ulong writer_space;
    const int tile_cache_space = 50 * 1024;
    const int min_image_rows = 2;
    int min_row_space =
        min_image_rows * (  4 * ( pdev->width + sizeof(int) - 1 )  );
    int min_band_height = max(1, pdev->height / 100);	/* make bands >= 1% of total */

    space_params->band.BandWidth = pdev->width;
    space_params->band.BandHeight = min_band_height;

    gdev_mem_data_size( (const gx_device_memory *)pdev, space_params->band.BandWidth,
                        space_params->band.BandHeight, &render_space );
    /* need to include minimal writer requirements to satisfy rasterizer init */
    writer_space = 	/* add 5K slop for good measure */
        5000 + (72 + 8) * ( (pdev->height / space_params->band.BandHeight) + 1 );
    space_params->band.BandBufferSpace =
        max(render_space, writer_space) + tile_cache_space;
    space_params->BufferSpace =
        max(render_space, writer_space + min_row_space) + tile_cache_space;
    /**************** HACK HACK HACK ****************/
    /* Override this computation to force reader & writer to match */
    space_params->BufferSpace = space_params->band.BandBufferSpace;
}

/* Get device parameters. */
static int
bmpa_get_params(gx_device * pdev, gs_param_list * plist)
{
    gx_device_async * const bdev = (gx_device_async *)pdev;

    return gdev_prn_get_params_planar(pdev, plist, &bdev->UsePlanarBuffer);
}

/* Put device parameters. */
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
static int
bmpa_put_params(gx_device *pdev, gs_param_list *plist)
{
    /*
     * This driver does nothing interesting except cascade down to
     * gdev_prn_put_params_planar, which is something it would have to do
     * even if it did do something interesting here.
     *
     * Note that gdev_prn_put_params[_planar] does not close the device.
     */
    gx_device_async * const bdev = (gx_device_async *)pdev;

    return gdev_prn_put_params_planar(pdev, plist, &bdev->UsePlanarBuffer);
}

/* Get hardware-detected parameters. */
/* This proc defines a only one param: a useless value for testing */
static int
bmpa_get_hardware_params(gx_device *dev, gs_param_list *plist)
{
    static const char *const test_value = "Test value";
    static const char *const test_name = "TestValue";
    int code = 0;

    if ( param_requested(plist, test_name) ) {
        gs_param_string param_str;

        param_string_from_string(param_str, test_value); /* value must be persistent to use this macro */
        code = param_write_string(plist, test_name, &param_str);
    }
    return code;
}