/* Copyright (C) 1992 Aladdin Enterprises. All rights reserved. Distributed by Free Software Foundation, Inc. This file is part of Ghostscript. Ghostscript is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. No author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all, unless he says so in writing. Refer to the Ghostscript General Public License for full details. Everyone is granted permission to copy, modify and redistribute Ghostscript, but only under the conditions described in the Ghostscript General Public License. A copy of this license is supposed to have been given to you along with Ghostscript so you can know your rights and responsibilities. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* gdevpbm.c */ /* Portable Bit/Gray/PixMap devices for Ghostscript. */ #include "gdevprn.h" #include "gxlum.h" /* Thanks are due to Jos Vos (jos@bull.nl) for an earlier P*M driver, */ /* on which this one is based. */ /* Structure for P*M devices, which extend the generic printer device. */ #define MAX_COMMENT 70 /* max user-supplied comment */ struct gx_device_pbm_s { gx_device_common; gx_prn_device_common; /* Additional state for P*M devices */ const char *magic; /* "Pn" */ char comment[MAX_COMMENT + 1]; /* comment for head of file */ byte is_raw; /* 1 if raw format, 0 if plain */ byte is_pbm; /* 1 if bitmap, 0 if gray or pixmap */ }; typedef struct gx_device_pbm_s gx_device_pbm; /* ------ The device descriptors ------ */ /* * Standard U.S. page width and height. A4 paper is 8.4" x 11.7". */ #define WIDTH_10THS 85 #define HEIGHT_10THS 110 /* * Default X and Y resolution. */ #define X_DPI 72 #define Y_DPI 72 /* Macro for generating P*M device descriptors. */ #define pbm_prn_device(procs, dev_name, magic, is_raw, num_comp, depth, max_gray, max_rgb, print_page)\ { prn_device_body(gx_device_pbm, procs, dev_name,\ WIDTH_10THS, HEIGHT_10THS, X_DPI, Y_DPI,\ 0, 0, 0, 0,\ num_comp, depth, max_gray, max_rgb, max_gray + 1, max_rgb + 1,\ print_page),\ magic,\ { 0 },\ is_raw,\ (depth == 1)\ } /* For all PBM variants we do some extra things at opening time. */ /* private dev_proc_open_device(gdev_pbm_open); */ #define gdev_pbm_open gdev_prn_open /* no we don't! */ /* For PGM and PPM we need our own color mapping procedures. */ private dev_proc_map_rgb_color(pgm_map_rgb_color); private dev_proc_map_rgb_color(ppm_map_rgb_color); private dev_proc_map_color_rgb(pgm_map_color_rgb); private dev_proc_map_color_rgb(ppm_map_color_rgb); /* And of course we need our own print-page routines. */ private dev_proc_print_page(pbm_print_page); private dev_proc_print_page(pbm_raw_print_page); private dev_proc_print_page(pgm_print_page); private dev_proc_print_page(ppm_print_page); /* The device procedures */ private gx_device_procs pbm_procs = prn_procs(gdev_pbm_open, gdev_prn_output_page, gdev_prn_close); private gx_device_procs pgm_procs = prn_color_procs(gdev_pbm_open, gdev_prn_output_page, gdev_prn_close, pgm_map_rgb_color, pgm_map_color_rgb); private gx_device_procs ppm_procs = prn_color_procs(gdev_pbm_open, gdev_prn_output_page, gdev_prn_close, ppm_map_rgb_color, ppm_map_color_rgb); /* The device descriptors themselves */ gx_device_pbm gs_pbm_device = pbm_prn_device(pbm_procs, "pbm", "P1", 0, 1, 1, 1, 0, pbm_print_page); gx_device_pbm gs_pbmraw_device = pbm_prn_device(pbm_procs, "pbmraw", "P4", 1, 1, 1, 1, 1, pbm_raw_print_page); gx_device_pbm gs_pgm_device = pbm_prn_device(pgm_procs, "pgm", "P2", 0, 1, 8, 255, 0, pgm_print_page); gx_device_pbm gs_pgmraw_device = pbm_prn_device(pgm_procs, "pgmraw", "P5", 1, 1, 8, 255, 0, pgm_print_page); gx_device_pbm gs_ppm_device = pbm_prn_device(ppm_procs, "ppm", "P3", 0, 3, 24, 255, 255, ppm_print_page); gx_device_pbm gs_ppmraw_device = pbm_prn_device(ppm_procs, "ppmraw", "P6", 1, 3, 24, 255, 255, ppm_print_page); /* ------ Color mapping routines ------ */ /* Map an RGB color to a PGM gray value. */ private gx_color_index pgm_map_rgb_color(gx_device *dev, ushort r, ushort g, ushort b) { /* We round the value rather than truncating it. */ return ((r * (ulong)lum_red_weight) + (g * (ulong)lum_green_weight) + (b * (ulong)lum_blue_weight) + (lum_all_weights / 2)) / lum_all_weights * dev->color_info.max_gray / gx_max_color_value; } /* Map a PGM gray value back to an RGB color. */ private int pgm_map_color_rgb(gx_device *dev, gx_color_index color, ushort prgb[3]) { gx_color_value gray = color * gx_max_color_value / dev->color_info.max_gray; prgb[0] = gray; prgb[1] = gray; prgb[2] = gray; return 0; } /* Map an RGB color to a PPM color tuple. */ private gx_color_index ppm_map_rgb_color(gx_device *dev, ushort r, ushort g, ushort b) { ushort bitspercolor = dev->color_info.depth / 3; ulong max_value = (1 << bitspercolor) - 1; return ((r * max_value / gx_max_color_value) << (bitspercolor * 2)) + ((g * max_value / gx_max_color_value) << bitspercolor) + (b * max_value / gx_max_color_value); } /* Map a PPM color tuple back to an RGB color. */ private int ppm_map_color_rgb(gx_device *dev, gx_color_index color, ushort prgb[3]) { ushort bitspercolor = dev->color_info.depth / 3; ushort colormask = (1 << bitspercolor) - 1; prgb[0] = ((color >> (bitspercolor * 2)) & colormask) * (ulong)gx_max_color_value / colormask; prgb[1] = ((color >> bitspercolor) & colormask) * (ulong)gx_max_color_value / colormask; prgb[2] = (color & colormask) * (ulong)gx_max_color_value / colormask; return 0; } /* ------ Internal routines ------ */ /* Define a "cursor" that keeps track of where we are in the page. */ typedef struct pbm_cursor_s { gx_device_pbm *dev; int bpp; /* bits per pixel */ uint line_size; /* bytes per scan line */ byte *data; /* output row buffer */ int lnum; /* row within page */ } pbm_cursor; /* Begin a P*M output page. */ /* Write the header information and initialize the cursor. */ private int pbm_begin_page(gx_device_pbm *bdev, FILE *pstream, pbm_cursor _ss *pcur) { uint line_size = gdev_prn_raster((gx_device_printer *)bdev); byte *data = (byte *)gs_malloc(line_size, 1, "pbm_begin_page"); if ( data == 0 ) return_error(gs_error_VMerror); fprintf(pstream, "%s\n", bdev->magic); if ( bdev->comment[0] ) fprintf(pstream, "# %s\n", bdev->comment); else fprintf(pstream, "# Image generated by Ghostscript (device=%s)\n", bdev->dname); fprintf(pstream, "%d %d\n", bdev->width, bdev->height); if ( !bdev->is_pbm ) fprintf(pstream, "%d\n", bdev->color_info.max_gray); /* Initialize the cursor. */ pcur->dev = bdev; pcur->bpp = bdev->color_info.depth; pcur->line_size = line_size; pcur->data = data; pcur->lnum = 0; return 0; } /* Advance to the next row. Return 0 if more, 1 if done. */ private int pbm_next_row(pbm_cursor _ss *pcur) { if ( pcur->lnum >= pcur->dev->height ) { gs_free((char *)pcur->data, pcur->line_size, 1, "pbm_next_row(done)"); return 1; } gdev_prn_copy_scan_lines((gx_device_printer *)pcur->dev, pcur->lnum++, pcur->data, pcur->line_size); return 0; } /* ------ Individual page printing routines ------ */ #define bdev ((gx_device_pbm *)pdev) /* Print a raw monobit page. */ private int pbm_raw_print_page(gx_device_printer *pdev, FILE *pstream) { /* All the print_page routines have the same structure; */ /* only the indicated section is different. */ pbm_cursor cur; int code = pbm_begin_page(bdev, pstream, &cur); if ( code < 0 ) return code; while ( !(code = pbm_next_row(&cur)) ) { /* ---- This section changes. ---- */ fwrite(cur.data, 1, cur.line_size, pstream); /* ---- End of changing section. ---- */ } return (code < 0 ? code : 0); } /* Print an ASCII monobit page. */ private int pbm_print_page(gx_device_printer *pdev, FILE *pstream) { pbm_cursor cur; int code = pbm_begin_page(bdev, pstream, &cur); if ( code < 0 ) return code; while ( !(code = pbm_next_row(&cur)) ) { byte *bp; uint x, mask; for ( bp = cur.data, x = 0, mask = 0x80; x < bdev->width; (mask >>= 1) != 0 || (bp++, mask = 0x80) ) { putc((*bp & mask ? '1' : '0'), pstream); if ( ++x == bdev->width || !(x & 63) ) putc('\n', pstream); } } return (code < 0 ? code : 0); } /* Print a gray-mapped page. */ private int pgm_print_page(gx_device_printer *pdev, FILE *pstream) { pbm_cursor cur; int code = pbm_begin_page(bdev, pstream, &cur); uint mask; if ( code < 0 ) return code; /* Note that bpp <= 8 for raw format, bpp <= 16 for plain. */ mask = (1 << cur.bpp) - 1; while ( !(code = pbm_next_row(&cur)) ) { byte *bp; uint x; int shift; for ( bp = cur.data, x = 0, shift = 8 - cur.bpp; x < bdev->width; ) { uint pixel; if ( shift < 0 ) /* bpp = 16 */ { pixel = ((uint)*bp << 8) + bp[1]; bp += 2; } else { pixel = (*bp >> shift) & mask; if ( (shift -= cur.bpp) < 0 ) bp++, shift += 8; } ++x; if ( bdev->is_raw ) putc(pixel, pstream); else fprintf(pstream, "%d%c", pixel, (x == bdev->width || !(x & 15) ? '\n' : ' ')); } } return (code < 0 ? code : 0); } /* Print a color-mapped page. */ private int ppm_print_page(gx_device_printer *pdev, FILE *pstream) { pbm_cursor cur; int code = pbm_begin_page(bdev, pstream, &cur); uint bpe, mask; if ( code < 0 ) return code; /* Note that bpp <= 24 for raw format, bpp <= 32 for plain. */ bpe = cur.bpp / 3; /* bits per r/g/b element */ mask = (1 << bpe) - 1; while ( !(code = pbm_next_row(&cur)) ) { byte *bp; uint x; int shift; for ( bp = cur.data, x = 0, shift = 8 - cur.bpp; x < bdev->width; ) { ulong pixel = 0; uint r, g, b; switch ( cur.bpp >> 3 ) { case 3: pixel = (ulong)*bp << 16; bp++; /* falls through */ case 2: pixel += (uint)*bp << 8; bp++; /* falls through */ case 1: pixel += *bp; bp++; break; case 0: /* bpp == 4, bpe == 1 */ pixel = *bp >> shift; if ( (shift -= cur.bpp) < 0 ) bp++, shift += 8; break; } ++x; b = pixel & mask; pixel >>= bpe; g = pixel & mask; pixel >>= bpe; r = pixel & mask; if ( bdev->is_raw ) { putc(r, pstream); putc(g, pstream); putc(b, pstream); } else fprintf(pstream, "%d %d %d%c", r, g, b, (x == bdev->width || !(x & 7) ? '\n' : ' ')); } } return (code < 0 ? code : 0); }