9 tiff.c - implements reading and writing tiff files, uses io layer.
13 io_glue *ig = io_new_fd( fd );
14 i_img *im = i_readtiff_wiol(ig, -1); // no limit on how much is read
16 io_glue *ig = io_new_fd( fd );
17 return_code = i_writetiff_wiol(im, ig);
21 tiff.c implements the basic functions to read and write tiff files.
22 It uses the iolayer and needs either a seekable source or an entire
25 =head1 FUNCTION REFERENCE
27 Some of these functions are internal.
35 #define byteswap_macro(x) \
36 ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
37 (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
44 static struct tag_name text_tag_names[] =
46 { "tiff_documentname", TIFFTAG_DOCUMENTNAME, },
47 { "tiff_imagedescription", TIFFTAG_IMAGEDESCRIPTION, },
48 { "tiff_make", TIFFTAG_MAKE, },
49 { "tiff_model", TIFFTAG_MODEL, },
50 { "tiff_pagename", TIFFTAG_PAGENAME, },
51 { "tiff_software", TIFFTAG_SOFTWARE, },
52 { "tiff_datetime", TIFFTAG_DATETIME, },
53 { "tiff_artist", TIFFTAG_ARTIST, },
54 { "tiff_hostcomputer", TIFFTAG_HOSTCOMPUTER, },
57 static const int text_tag_count =
58 sizeof(text_tag_names) / sizeof(*text_tag_names);
60 static void error_handler(char const *module, char const *fmt, va_list ap) {
61 i_push_errorvf(0, fmt, ap);
64 static int save_tiff_tags(TIFF *tif, i_img *im);
66 static void expand_4bit_hl(unsigned char *buf, int count);
69 =item comp_seek(h, o, w)
71 Compatability for 64 bit systems like latest freebsd (internal)
73 h - tiff handle, cast an io_glue object
82 comp_seek(thandle_t h, toff_t o, int w) {
83 io_glue *ig = (io_glue*)h;
84 return (toff_t) ig->seekcb(ig, o, w);
89 =item i_readtiff_wiol(ig, length)
91 Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
94 length - maximum length to read from data source, before closing it
100 i_readtiff_wiol(io_glue *ig, int length) {
102 uint32 width, height;
104 uint32* raster = NULL;
109 int gotXres, gotYres;
111 uint16 bits_per_sample;
114 TIFFErrorHandler old_handler;
117 old_handler = TIFFSetErrorHandler(error_handler);
121 /* Add code to get the filename info from the iolayer */
122 /* Also add code to check for mmapped code */
124 io_glue_commit_types(ig);
125 mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
127 tif = TIFFClientOpen("(Iolayer)",
130 (TIFFReadWriteProc) ig->readcb,
131 (TIFFReadWriteProc) ig->writecb,
132 (TIFFSeekProc) comp_seek,
133 (TIFFCloseProc) ig->closecb,
134 (TIFFSizeProc) ig->sizecb,
135 (TIFFMapFileProc) NULL,
136 (TIFFUnmapFileProc) NULL);
139 mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
140 i_push_error(0, "opening file");
141 TIFFSetErrorHandler(old_handler);
145 TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
146 TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
147 TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
148 tiled = TIFFIsTiled(tif);
149 TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
150 TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
152 mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels));
153 mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not "));
154 mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not "));
156 if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) {
158 im = i_img_pal_new(width, height, channels, 256);
161 im = i_img_empty_ch(NULL, width, height, channels);
164 /* resolution tags */
165 TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
166 gotXres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres);
167 gotYres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres);
168 if (gotXres || gotYres) {
173 if (resunit == RESUNIT_CENTIMETER) {
174 /* from dots per cm to dpi */
178 i_tags_addn(&im->tags, "tiff_resolutionunit", 0, resunit);
179 if (resunit == RESUNIT_NONE)
180 i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
181 i_tags_set_float(&im->tags, "i_xres", 0, xres);
182 i_tags_set_float(&im->tags, "i_yres", 0, yres);
186 for (i = 0; i < text_tag_count; ++i) {
188 if (TIFFGetField(tif, text_tag_names[i].tag, &data)) {
189 mm_log((1, "i_readtiff_wiol: tag %d has value %s\n",
190 text_tag_names[i].tag, data));
191 i_tags_add(&im->tags, text_tag_names[i].name, 0, data,
196 /* TIFFPrintDirectory(tif, stdout, 0); good for debugging */
198 if (photometric == PHOTOMETRIC_PALETTE &&
199 (bits_per_sample == 4 || bits_per_sample == 8)) {
204 unsigned char *buffer;
206 if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
207 i_push_error(0, "Cannot get colormap for paletted image");
208 TIFFSetErrorHandler(old_handler);
213 buffer = (unsigned char *)_TIFFmalloc(width+2);
215 i_push_error(0, "out of memory");
216 TIFFSetErrorHandler(old_handler);
222 memset(used, 0, sizeof(used));
223 while (row < height && TIFFReadScanline(tif, buffer, row, 0) > 0) {
224 if (bits_per_sample == 4)
225 expand_4bit_hl(buffer, (width+1)/2);
226 for (col = 0; col < width; ++col) {
227 used[buffer[col]] = 1;
229 i_ppal(im, 0, width, row, buffer);
235 /* Ideally we'd optimize the palette, but that could be expensive
236 since we'd have to re-index every pixel.
238 Optimizing the palette (even at this level) might not
239 be what the user wants, so I don't do it.
241 We'll add a function to optimize a paletted image instead.
243 maxused = (1 << bits_per_sample)-1;
245 while (maxused >= 0 && !used[maxused])
248 for (i = 0; i < 1 << bits_per_sample; ++i) {
250 for (ch = 0; ch < 3; ++ch) {
251 c.channel[ch] = Sample16To8(maps[ch][i]);
253 i_addcolors(im, &c, 1);
261 uint32 tile_width, tile_height;
263 TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
264 TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
265 mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
267 raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
270 i_push_error(0, "No space for raster buffer");
271 TIFFSetErrorHandler(old_handler);
276 for( row = 0; row < height; row += tile_height ) {
277 for( col = 0; ok && col < width; col += tile_width ) {
278 uint32 i_row, x, newrows, newcols;
280 /* Read the tile into an RGBA array */
281 if (!TIFFReadRGBATile(tif, col, row, raster)) {
285 newrows = (row+tile_height > height) ? height-row : tile_height;
286 mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows));
287 newcols = (col+tile_width > width ) ? width-row : tile_width;
288 for( i_row = 0; i_row < tile_height; i_row++ ) {
289 for(x = 0; x < newcols; x++) {
291 uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
292 val.rgba.r = TIFFGetR(temp);
293 val.rgba.g = TIFFGetG(temp);
294 val.rgba.b = TIFFGetB(temp);
295 val.rgba.a = TIFFGetA(temp);
296 i_ppix(im, col+x, row+i_row, &val);
302 uint32 rowsperstrip, row;
303 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
304 mm_log((1, "i_readtiff_wiol: rowsperstrip=%d\n", rowsperstrip));
306 raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
309 i_push_error(0, "No space for raster buffer");
310 TIFFSetErrorHandler(old_handler);
315 for( row = 0; row < height; row += rowsperstrip ) {
316 uint32 newrows, i_row;
318 if (!TIFFReadRGBAStrip(tif, row, raster)) {
323 newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
324 mm_log((1, "newrows=%d\n", newrows));
326 for( i_row = 0; i_row < newrows; i_row++ ) {
328 for(x = 0; x<width; x++) {
330 uint32 temp = raster[x+width*(newrows-i_row-1)];
331 val.rgba.r = TIFFGetR(temp);
332 val.rgba.g = TIFFGetG(temp);
333 val.rgba.b = TIFFGetB(temp);
334 val.rgba.a = TIFFGetA(temp);
335 i_ppix(im, x, i_row+row, &val);
342 mm_log((1, "i_readtiff_wiol: error during reading\n"));
343 i_tags_addn(&im->tags, "i_incomplete", 0, 1);
347 if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
348 TIFFSetErrorHandler(old_handler);
356 =item i_writetif_wiol(im, ig)
358 Stores an image in the iolayer object.
360 im - image object to write out
361 ig - io_object that defines source to write to
366 /* FIXME: Add an options array in here soonish */
369 i_writetiff_wiol(i_img *im, io_glue *ig) {
370 uint32 width, height;
372 uint16 predictor = 0;
374 int jpegcolormode = JPEGCOLORMODE_RGB;
375 uint16 compression = COMPRESSION_PACKBITS;
378 uint32 rowsperstrip = (uint32) -1; /* Let library pick default */
379 unsigned char *linebuf = NULL;
385 int got_xres, got_yres, got_aspectonly, aspect_only, resunit;
388 char *cc = mymalloc( 123 );
394 channels = im->channels;
398 photometric = PHOTOMETRIC_MINISBLACK;
401 photometric = PHOTOMETRIC_RGB;
402 if (compression == COMPRESSION_JPEG && jpegcolormode == JPEGCOLORMODE_RGB) photometric = PHOTOMETRIC_YCBCR;
405 /* This means a colorspace we don't handle yet */
406 mm_log((1, "i_writetiff_wiol: don't handle %d channel images.\n", channels));
410 /* Add code to get the filename info from the iolayer */
411 /* Also add code to check for mmapped code */
413 io_glue_commit_types(ig);
414 mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));
416 /* FIXME: Enable the mmap interface */
418 tif = TIFFClientOpen("No name",
421 (TIFFReadWriteProc) ig->readcb,
422 (TIFFReadWriteProc) ig->writecb,
423 (TIFFSeekProc) comp_seek,
424 (TIFFCloseProc) ig->closecb,
425 (TIFFSizeProc) ig->sizecb,
426 (TIFFMapFileProc) NULL,
427 (TIFFUnmapFileProc) NULL);
432 mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
436 mm_log((1, "i_writetiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels));
438 if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); return 0; }
439 if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); return 0; }
440 if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, channels)) { mm_log((1, "i_writetiff_wiol: TIFFSetField samplesperpixel=%d failed\n", channels)); return 0; }
441 if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) { mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); return 0; }
442 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=8\n")); return 0; }
443 if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); return 0; }
444 if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) { mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric)); return 0; }
445 if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)) { mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); return 0; }
447 switch (compression) {
448 case COMPRESSION_JPEG:
449 mm_log((1, "i_writetiff_wiol: jpeg compression\n"));
450 if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality)); return 0; }
451 if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) { mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode)); return 0; }
453 case COMPRESSION_LZW:
454 mm_log((1, "i_writetiff_wiol: lzw compression\n"));
456 case COMPRESSION_DEFLATE:
457 mm_log((1, "i_writetiff_wiol: deflate compression\n"));
459 if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) { mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor)); return 0; }
461 case COMPRESSION_PACKBITS:
462 mm_log((1, "i_writetiff_wiol: packbits compression\n"));
465 mm_log((1, "i_writetiff_wiol: unknown compression %d\n", compression));
469 linebytes = channels * width;
470 linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) > linebytes ?
471 linebytes : TIFFScanlineSize(tif) );
473 if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, rowsperstrip))) {
474 mm_log((1, "i_writetiff_wiol: TIFFSetField rowsperstrip=%d\n", rowsperstrip)); return 0; }
476 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
477 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
479 mm_log((1, "i_writetiff_wiol: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
480 mm_log((1, "i_writetiff_wiol: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
481 mm_log((1, "i_writetiff_wiol: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
483 got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
484 got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
485 if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
487 if (!i_tags_get_int(&im->tags, "tiff_resolutionunit", 0, &resunit))
488 resunit = RESUNIT_INCH;
489 if (got_xres || got_yres) {
495 resunit = RESUNIT_NONE;
498 if (resunit == RESUNIT_CENTIMETER) {
503 resunit = RESUNIT_INCH;
506 if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
509 i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag");
512 if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
515 i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag");
518 if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
521 i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag");
526 if (!save_tiff_tags(tif, im)) {
531 for (y=0; y<height; y++) {
533 for(x=0; x<width; x++) {
534 (void) i_gpix(im, x, y,&val);
535 for(ch=0; ch<channels; ch++) linebuf[ci++] = val.channel[ch];
537 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
538 mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
542 (void) TIFFClose(tif);
543 if (linebuf) _TIFFfree(linebuf);
548 =item i_writetiff_wiol_faxable(i_img *, io_glue *)
550 Stores an image in the iolayer object in faxable tiff format.
552 im - image object to write out
553 ig - io_object that defines source to write to
555 Note, this may be rewritten to use to simply be a call to a
556 lower-level function that gives more options for writing tiff at some
563 i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
564 uint32 width, height;
565 unsigned char *linebuf = NULL;
572 float vres = fine ? 196 : 98;
578 switch (im->channels) {
588 /* This means a colorspace we don't handle yet */
589 mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
593 /* Add code to get the filename info from the iolayer */
594 /* Also add code to check for mmapped code */
596 io_glue_commit_types(ig);
597 mm_log((1, "i_writetiff_wiol_faxable(im 0x%p, ig 0x%p)\n", im, ig));
599 /* FIXME: Enable the mmap interface */
601 tif = TIFFClientOpen("No name",
604 (TIFFReadWriteProc) ig->readcb,
605 (TIFFReadWriteProc) ig->writecb,
606 (TIFFSeekProc) comp_seek,
607 (TIFFCloseProc) ig->closecb,
608 (TIFFSizeProc) ig->sizecb,
609 (TIFFMapFileProc) NULL,
610 (TIFFUnmapFileProc) NULL);
613 mm_log((1, "i_writetiff_wiol_faxable: Unable to open tif file for writing\n"));
617 mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
619 if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) )
620 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
621 if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) )
622 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
623 if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
624 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
625 if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT))
626 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
627 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1) )
628 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
629 if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
630 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
631 if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK))
632 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
633 if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
634 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }
636 linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
638 if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
639 mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }
641 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
642 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
644 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
645 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
646 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
648 if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
649 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
650 if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
651 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
652 if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
653 mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0;
656 if (!save_tiff_tags(tif, im)) {
661 for (y=0; y<height; y++) {
663 for(x=0; x<width; x+=8) {
668 linebuf[linebufpos]=0;
669 bits = width-x; if(bits>8) bits=8;
670 i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
671 for(bitpos=0;bitpos<bits;bitpos++) {
672 linebuf[linebufpos] |= ((luma[bitpos]>=128)?bitval:0);
677 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
678 mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
682 (void) TIFFClose(tif);
683 if (linebuf) _TIFFfree(linebuf);
687 static int save_tiff_tags(TIFF *tif, i_img *im) {
690 for (i = 0; i < text_tag_count; ++i) {
692 if (i_tags_find(&im->tags, text_tag_names[i].name, 0, &entry)) {
693 if (!TIFFSetField(tif, text_tag_names[i].tag,
694 im->tags.tags[entry].data)) {
695 i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name);
705 =item expand_4bit_hl(buf, count)
707 Expands 4-bit/entry packed data into 1 byte/entry.
709 buf must contain count bytes to be expanded and have 2*count bytes total
712 The data is expanded in place.
717 static void expand_4bit_hl(unsigned char *buf, int count) {
718 while (--count >= 0) {
719 buf[count*2+1] = buf[count] & 0xF;
720 buf[count*2] = buf[count] >> 4;
730 Arnar M. Hrafnkelsson <addi@umich.edu>