xref: /haiku/src/libs/print/libprint/GraphicsDriver.cpp (revision 37c7d5d83a2372a6971e383411d5bacbeef0ebdc)
1 /*
2  * GraphicsDriver.cpp
3  * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
4  */
5 
6 #include <algorithm>
7 #include <cstdio>
8 #include <cstdarg>
9 
10 #include <Alert.h>
11 #include <Bitmap.h>
12 #include <Message.h>
13 #include <PrintJob.h>
14 #include <Region.h>
15 #include <TextControl.h>
16 #include <TextControl.h>
17 #include <StopWatch.h>
18 #include <View.h>
19 #include <Directory.h>
20 #include <File.h>
21 
22 #include "GraphicsDriver.h"
23 #include "PrintProcess.h"
24 #include "JobData.h"
25 #include "PrinterData.h"
26 #include "PrinterCap.h"
27 #include "Preview.h"
28 #include "Transport.h"
29 #include "ValidRect.h"
30 #include "DbgMsg.h"
31 
32 #if (!__MWERKS__ || defined(MSIPL_USING_NAMESPACE))
33 using namespace std;
34 #else
35 #define std
36 #endif
37 
38 // Measure printJob() time. Either true or false.
39 #define MEASURE_PRINT_JOB_TIME false
40 
41 enum {
42 	kMaxMemorySize = (4 *1024 *1024)
43 };
44 
45 GraphicsDriver::GraphicsDriver(BMessage *msg, PrinterData *printer_data, const PrinterCap *printer_cap)
46 	: fMsg(msg)
47 	, fPrinterData(printer_data)
48 	, fPrinterCap(printer_cap)
49 {
50 	fView          = NULL;
51 	fBitmap        = NULL;
52 	fTransport     = NULL;
53 	fOrgJobData    = NULL;
54 	fRealJobData   = NULL;
55 	fSpoolMetaData = NULL;
56 }
57 
58 GraphicsDriver::~GraphicsDriver()
59 {
60 }
61 
62 
63 static BRect RotateRect(BRect rect)
64 {
65 	BRect rotated(rect.top, rect.left, rect.bottom, rect.right);
66 	return rotated;
67 }
68 
69 bool
70 GraphicsDriver::setupData(BFile *spoolFile)
71 {
72 	if (fOrgJobData != NULL) {
73 		// already initialized
74 		return true;
75 	}
76 
77 #ifndef B_BEOS_VERSION_DANO
78 	print_file_header pfh;
79 #else
80 	BPrintJob::print_file_header pfh;
81 #endif
82 	spoolFile->Seek(0, SEEK_SET);
83 	spoolFile->Read(&pfh, sizeof(pfh));
84 
85 	DBGMSG(("print_file_header::version = 0x%x\n",  pfh.version));
86 	DBGMSG(("print_file_header::page_count = %d\n", pfh.page_count));
87 	DBGMSG(("print_file_header::first_page = 0x%x\n", (int)pfh.first_page));
88 
89 	if (pfh.page_count <= 0) {
90 		// nothing to print
91 		return false;
92 	}
93 
94 	fPageCount = (uint32) pfh.page_count;
95 	BMessage *msg = new BMessage();
96 	msg->Unflatten(spoolFile);
97 	fOrgJobData = new JobData(msg, fPrinterCap, JobData::kJobSettings);
98 	DUMP_BMESSAGE(msg);
99 	delete msg;
100 
101 	fRealJobData = new JobData(*fOrgJobData);
102 
103 	switch (fOrgJobData->getNup()) {
104 	case 2:
105 	case 8:
106 	case 32:
107 	case 128:
108 		fRealJobData->setPrintableRect(RotateRect(fOrgJobData->getPrintableRect()));
109 		fRealJobData->setScaledPrintableRect(RotateRect(fOrgJobData->getScaledPrintableRect()));
110 		fRealJobData->setPhysicalRect(RotateRect(fOrgJobData->getPhysicalRect()));
111 		fRealJobData->setScaledPhysicalRect(RotateRect(fOrgJobData->getScaledPhysicalRect()));
112 		if (JobData::kPortrait == fOrgJobData->getOrientation())
113 			fRealJobData->setOrientation(JobData::kLandscape);
114 		else
115 			fRealJobData->setOrientation(JobData::kPortrait);
116 		break;
117 	}
118 
119 	if (fOrgJobData->getCollate() && fPageCount > 1) {
120 		fRealJobData->setCopies(1);
121 		fInternalCopies = fOrgJobData->getCopies();
122 	} else {
123 		fInternalCopies = 1;
124 	}
125 
126 	fSpoolMetaData = new SpoolMetaData(spoolFile);
127 	return true;
128 }
129 
130 void
131 GraphicsDriver::cleanupData()
132 {
133 	delete fRealJobData;
134 	delete fOrgJobData;
135 	delete fSpoolMetaData;
136 	fRealJobData   = NULL;
137 	fOrgJobData    = NULL;
138 	fSpoolMetaData = NULL;
139 }
140 
141 void
142 GraphicsDriver::setupBitmap()
143 {
144 	fPixelDepth = color_space2pixel_depth(fOrgJobData->getSurfaceType());
145 
146 	fPageWidth  = (fRealJobData->getPhysicalRect().IntegerWidth()  * fOrgJobData->getXres() + 71) / 72;
147 	fPageHeight = (fRealJobData->getPhysicalRect().IntegerHeight() * fOrgJobData->getYres() + 71) / 72;
148 
149 	int widthByte = (fPageWidth * fPixelDepth + 7) / 8;
150 	int size = widthByte * fPageHeight;
151 #ifdef USE_PREVIEW_FOR_DEBUG
152 	size = 0;
153 #endif
154 
155 	if (size < kMaxMemorySize) {
156 		fBandCount  = 0;
157 		fBandWidth  = fPageWidth;
158 		fBandHeight = fPageHeight;
159 	} else {
160 		fBandCount  = (size + kMaxMemorySize - 1) / kMaxMemorySize;
161 		if ((JobData::kLandscape == fRealJobData->getOrientation()) && (fFlags & kGDFRotateBandBitmap)) {
162 			fBandWidth  = (fPageWidth + fBandCount - 1) / fBandCount;
163 			fBandHeight = fPageHeight;
164 		} else {
165 			fBandWidth  = fPageWidth;
166 			fBandHeight = (fPageHeight + fBandCount - 1) / fBandCount;
167 		}
168 	}
169 
170 	DBGMSG(("****************\n"));
171 	DBGMSG(("page_width  = %d\n", fPageWidth));
172 	DBGMSG(("page_height = %d\n", fPageHeight));
173 	DBGMSG(("band_count  = %d\n", fBandCount));
174 	DBGMSG(("band_height = %d\n", fBandHeight));
175 	DBGMSG(("****************\n"));
176 
177 	BRect rect;
178 	rect.Set(0, 0, fBandWidth - 1, fBandHeight - 1);
179 	fBitmap = new BBitmap(rect, fOrgJobData->getSurfaceType(), true);
180 	fView   = new BView(rect, "", B_FOLLOW_ALL, B_WILL_DRAW);
181 	fBitmap->AddChild(fView);
182 }
183 
184 void
185 GraphicsDriver::cleanupBitmap()
186 {
187 	delete fBitmap;
188 	fBitmap = NULL;
189 	fView   = NULL;
190 }
191 
192 BPoint
193 GraphicsDriver::getScale(int32 nup, BRect physicalRect, float scaling)
194 {
195 	float width;
196 	float height;
197 	BPoint scale;
198 
199 	scale.x = scale.y = 1.0f;
200 
201 	switch (nup) {
202 	case 1:
203 		scale.x = scale.y = 1.0f;
204 		break;
205 	case 2:	/* 1x2 or 2x1 */
206 		width  = physicalRect.Width();
207 		height = physicalRect.Height();
208 		if (width < height) {	// portrait
209 			scale.x = height / 2.0f / width;
210 			scale.y = width / height;
211 		} else {	// landscape
212 			scale.x = height / width;
213 			scale.y = width / 2.0f / height;
214 		}
215 		break;
216 	case 8:	/* 2x4 or 4x2 */
217 		width  = physicalRect.Width();
218 		height = physicalRect.Height();
219 		if (width < height) {
220 			scale.x = height / 4.0f / width;
221 			scale.y = width / height / 2.0f;
222 		} else {
223 			scale.x = height / width / 2.0f;
224 			scale.y = width / 4.0f / height;
225 		}
226 		break;
227 	case 32:	/* 4x8 or 8x4 */
228 		width  = physicalRect.Width();
229 		height = physicalRect.Height();
230 		if (width < height) {
231 			scale.x = height / 8.0f / width;
232 			scale.y = width / height / 4.0f;
233 		} else {
234 			scale.x = height / width / 4.0f;
235 			scale.y = width / 8.0f / height;
236 		}
237 		break;
238 	case 4:		/* 2x2 */
239 		scale.x = scale.y = 1.0f / 2.0f;
240 		break;
241 	case 9:		/* 3x3 */
242 		scale.x = scale.y = 1.0f / 3.0f;
243 		break;
244 	case 16:	/* 4x4 */
245 		scale.x = scale.y = 1.0f / 4.0f;
246 		break;
247 	case 25:	/* 5x5 */
248 		scale.x = scale.y = 1.0f / 5.0f;
249 		break;
250 	case 36:	/* 6x6 */
251 		scale.x = scale.y = 1.0f / 6.0f;
252 		break;
253 	case 49:	/* 7x7 */
254 		scale.x = scale.y = 1.0f / 7.0f;
255 		break;
256 	case 64:	/* 8x8 */
257 		scale.x = scale.y = 1.0f / 8.0f;
258 		break;
259 	case 81:	/* 9x9 */
260 		scale.x = scale.y = 1.0f / 9.0f;
261 		break;
262 	case 100:	/* 10x10 */
263 		scale.x = scale.y = 1.0f / 10.0f;
264 		break;
265 	case 121:	/* 11x11 */
266 		scale.x = scale.y = 1.0f / 11.0f;
267 		break;
268 	}
269 
270 	scale.x = scale.x * scaling / 100.0;
271 	scale.y = scale.y * scaling / 100.0;
272 
273 	return scale;
274 }
275 
276 BPoint
277 GraphicsDriver::getOffset(int32 nup, int index, JobData::Orientation orientation,
278 	const BPoint *scale, BRect scaledPhysicalRect, BRect scaledPrintableRect,
279 	BRect physicalRect)
280 {
281 	BPoint offset;
282 	offset.x = 0;
283 	offset.y = 0;
284 
285 	float width  = scaledPhysicalRect.Width();
286 	float height = scaledPhysicalRect.Height();
287 
288 	switch (nup) {
289 	case 1:
290 		break;
291 	case 2:
292 		if (index == 1) {
293 			if (JobData::kPortrait == orientation) {
294 				offset.x = width;
295 			} else {
296 				offset.y = height;
297 			}
298 		}
299 		break;
300 	case 8:
301 		if (JobData::kPortrait == orientation) {
302 			offset.x = width  * (index / 2);
303 			offset.y = height * (index % 2);
304 		} else {
305 			offset.x = width  * (index % 2);
306 			offset.y = height * (index / 2);
307 		}
308 		break;
309 	case 32:
310 		if (JobData::kPortrait == orientation) {
311 			offset.x = width  * (index / 4);
312 			offset.y = height * (index % 4);
313 		} else {
314 			offset.x = width  * (index % 4);
315 			offset.y = height * (index / 4);
316 		}
317 		break;
318 	case 4:
319 		offset.x = width  * (index / 2);
320 		offset.y = height * (index % 2);
321 		break;
322 	case 9:
323 		offset.x = width  * (index / 3);
324 		offset.y = height * (index % 3);
325 		break;
326 	case 16:
327 		offset.x = width  * (index / 4);
328 		offset.y = height * (index % 4);
329 		break;
330 	case 25:
331 		offset.x = width  * (index / 5);
332 		offset.y = height * (index % 5);
333 		break;
334 	case 36:
335 		offset.x = width  * (index / 6);
336 		offset.y = height * (index % 6);
337 		break;
338 	case 49:
339 		offset.x = width  * (index / 7);
340 		offset.y = height * (index % 7);
341 		break;
342 	case 64:
343 		offset.x = width  * (index / 8);
344 		offset.y = height * (index % 8);
345 		break;
346 	case 81:
347 		offset.x = width  * (index / 9);
348 		offset.y = height * (index % 9);
349 		break;
350 	case 100:
351 		offset.x = width  * (index / 10);
352 		offset.y = height * (index % 10);
353 		break;
354 	case 121:
355 		offset.x = width  * (index / 11);
356 		offset.y = height * (index % 11);
357 		break;
358 	}
359 
360 	// adjust margin
361 	offset.x += scaledPrintableRect.left - physicalRect.left;
362 	offset.y += scaledPrintableRect.top - physicalRect.top;
363 
364 	float real_scale = min(scale->x, scale->y);
365 	if (real_scale != scale->x)
366 		offset.x *= scale->x / real_scale;
367 	else
368 		offset.y *= scale->y / real_scale;
369 
370 	return offset;
371 }
372 
373 // print the specified pages on a physical page
374 bool
375 GraphicsDriver::printPage(PageDataList *pages)
376 {
377 	BPoint offset;
378 	offset.x = 0.0f;
379 	offset.y = 0.0f;
380 
381 	if (pages == NULL) {
382 		return true;
383 	}
384 
385 	do {
386 		// clear the physical page
387 		fView->SetScale(1.0);
388 		fView->SetHighColor(255, 255, 255);
389 		fView->ConstrainClippingRegion(NULL);
390 		fView->FillRect(fView->Bounds());
391 
392 		BPoint scale = getScale(fOrgJobData->getNup(), fOrgJobData->getPhysicalRect(), fOrgJobData->getScaling());
393 		float real_scale = min(scale.x, scale.y) * fOrgJobData->getXres() / 72.0f;
394 		fView->SetScale(real_scale);
395 		float x = offset.x / real_scale;
396 		float y = offset.y / real_scale;
397 		int page_index = 0;
398 
399 		for (PageDataList::iterator it = pages->begin(); it != pages->end(); it++) {
400 			BPoint left_top(getOffset(fOrgJobData->getNup(), page_index++, fOrgJobData->getOrientation(), &scale, fOrgJobData->getScaledPhysicalRect(), fOrgJobData->getScaledPrintableRect(), fOrgJobData->getPhysicalRect()));
401 			left_top.x -= x;
402 			left_top.y -= y;
403 			BRect clip(fOrgJobData->getScaledPrintableRect());
404 			clip.OffsetTo(left_top);
405 			BRegion *region = new BRegion();
406 			region->Set(clip);
407 			fView->ConstrainClippingRegion(region);
408 			delete region;
409 			if ((*it)->startEnum()) {
410 				bool more;
411 				do {
412 					PictureData	*picture_data;
413 					more = (*it)->enumObject(&picture_data);
414 					BPoint real_offset = left_top + picture_data->point;
415 					fView->DrawPicture(picture_data->picture, real_offset);
416 					fView->Sync();
417 					delete picture_data;
418 				} while (more);
419 			}
420 		}
421 		if (!nextBand(fBitmap, &offset)) {
422 			return false;
423 		}
424 	} while (offset.x >= 0.0f && offset.y >= 0.0f);
425 
426 	return true;
427 }
428 
429 bool
430 GraphicsDriver::collectPages(SpoolData *spool_data, PageDataList *pages)
431 {
432 	// collect the pages to be printed on the physical page
433 	PageData *page_data;
434 	int nup = fOrgJobData->getNup();
435 	bool more;
436 	do {
437 		more = spool_data->enumObject(&page_data);
438 		if (pages != NULL) {
439 			pages->push_back(page_data);
440 		}
441 	} while (more && --nup);
442 
443 	return more;
444 }
445 
446 bool
447 GraphicsDriver::skipPages(SpoolData *spool_data)
448 {
449 	return collectPages(spool_data, NULL);
450 }
451 
452 bool
453 GraphicsDriver::printDocument(SpoolData *spool_data)
454 {
455 	bool more;
456 	bool success;
457 	int page_index;
458 	int copy;
459 	int copies;
460 
461 	more = true;
462 	success = true;
463 	page_index = 0;
464 
465 	if (fPrinterCap->isSupport(PrinterCap::kCopyCommand)) {
466 		// let the printer perform the copy operation
467 		copies = 1;
468 	} else {
469 		// send the page multiple times to the printer
470 		copies = fRealJobData->getCopies();
471 	}
472 	fStatusWindow -> SetPageCopies(copies);						// inform fStatusWindow about number of copies
473 
474 	// printing of even/odd numbered pages only is valid in simplex mode
475 	bool simplex = fRealJobData->getPrintStyle() == JobData::kSimplex;
476 
477 	if (spool_data->startEnum()) {
478 		do {
479 			DBGMSG(("page index = %d\n", page_index));
480 
481 			if (simplex &&
482 				fRealJobData->getPageSelection() == JobData::kEvenNumberedPages) {
483 				// skip odd numbered page
484 				more = skipPages(spool_data);
485 			}
486 
487 			if (!more) {
488 				// end reached
489 				break;
490 			}
491 
492 			PageDataList pages;
493 			more = collectPages(spool_data, &pages);
494 
495 			if (more && simplex &&
496 				fRealJobData->getPageSelection() == JobData::kOddNumberedPages) {
497 				// skip even numbered page
498 				more = skipPages(spool_data);
499 			}
500 
501 			// print each physical page "copies" of times
502 			for (copy = 0; success && copy < copies; copy ++) {
503 
504 				// Update the status / cancel job
505 				if (fStatusWindow->UpdateStatusBar(page_index, copy))
506 					return false;
507 
508 				success = startPage(page_index);
509 				if (!success)
510 					break;
511 
512 				// print the pages on the physical page
513 				fView->Window()->Lock();
514 				success = printPage(&pages);
515 				fView->Window()->Unlock();
516 
517 				if (success) {
518 					success = endPage(page_index);
519 				}
520 			}
521 
522 			page_index++;
523 		} while (success && more);
524 	}
525 
526 #ifndef USE_PREVIEW_FOR_DEBUG
527 	if (success
528 		&& fPrinterCap->isSupport(PrinterCap::kPrintStyle)
529 		&& (fOrgJobData->getPrintStyle() != JobData::kSimplex)
530 		&& (((page_index + fOrgJobData->getNup() - 1) / fOrgJobData->getNup()) % 2))
531 	{
532 		// append an empty page on the back side of the page in duplex or booklet mode
533 		success =
534 			startPage(page_index) &&
535 			printPage(NULL) &&
536 			endPage(page_index);
537 	}
538 #endif
539 
540 	return success;
541 }
542 
543 const JobData*
544 GraphicsDriver::getJobData(BFile *spoolFile)
545 {
546 	setupData(spoolFile);
547 	return fOrgJobData;
548 }
549 
550 bool
551 GraphicsDriver::printJob(BFile *spoolFile)
552 {
553 	bool success = true;
554 	if (!setupData(spoolFile)) {
555 		// silently exit if there is nothing to print
556 		return true;
557 	}
558 
559 	fTransport = new Transport(fPrinterData);
560 
561 	if (fTransport->check_abort()) {
562 		success = false;
563 	} else if (!fTransport->is_print_to_file_canceled()) {
564 		BStopWatch stopWatch("printJob", !MEASURE_PRINT_JOB_TIME);
565 		setupBitmap();
566 		SpoolData spool_data(spoolFile, fPageCount, fOrgJobData->getNup(), fOrgJobData->getReverse());
567 		success = startDoc();
568 		if (success) {
569 			fStatusWindow = new StatusWindow(
570 				fRealJobData->getPageSelection() == JobData::kOddNumberedPages,
571 				fRealJobData->getPageSelection() == JobData::kEvenNumberedPages,
572 				fRealJobData->getFirstPage(),
573 				fPageCount,
574 				fInternalCopies,fRealJobData->getNup());
575 
576 			while (fInternalCopies--) {
577 				success = printDocument(&spool_data);
578 				if (success == false) {
579 					break;
580 				}
581 			}
582 			endDoc(success);
583 
584 			fStatusWindow->Lock();
585 			fStatusWindow->Quit();
586 		}
587 		cleanupBitmap();
588 		cleanupData();
589 	}
590 
591 	if (success == false) {
592 		BAlert *alert;
593 		if (fTransport->check_abort()) {
594 			alert = new BAlert("", fTransport->last_error().c_str(), "OK");
595 		} else {
596 			alert = new BAlert("", "Printer not responding.", "OK");
597 		}
598 		alert->Go();
599 	}
600 
601 	delete fTransport;
602 	fTransport = NULL;
603 
604 	return success;
605 }
606 
607 BMessage *
608 GraphicsDriver::takeJob(BFile* spoolFile, uint32 flags)
609 {
610 	fFlags = flags;
611 	BMessage *msg;
612 	if (printJob(spoolFile)) {
613 		msg = new BMessage('okok');
614 	} else {
615 		msg = new BMessage('baad');
616 	}
617 	return msg;
618 }
619 
620 bool
621 GraphicsDriver::startDoc()
622 {
623 	return true;
624 }
625 
626 bool
627 GraphicsDriver::startPage(int)
628 {
629 	return true;
630 }
631 
632 bool
633 GraphicsDriver::nextBand(BBitmap *, BPoint *)
634 {
635 	return true;
636 }
637 
638 bool
639 GraphicsDriver::endPage(int)
640 {
641 	return true;
642 }
643 
644 bool
645 GraphicsDriver::endDoc(bool)
646 {
647 	return true;
648 }
649 
650 void
651 GraphicsDriver::writeSpoolData(const void *buffer, size_t size) throw(TransportException)
652 {
653 	if (fTransport) {
654 		fTransport->write(buffer, size);
655 	}
656 }
657 
658 void
659 GraphicsDriver::writeSpoolString(const char *format, ...) throw(TransportException)
660 {
661 	if (fTransport) {
662 		char buffer[256];
663 		va_list	ap;
664 		va_start(ap, format);
665 		vsprintf(buffer, format, ap);
666 		fTransport->write(buffer, strlen(buffer));
667 		va_end(ap);
668 	}
669 }
670 
671 void
672 GraphicsDriver::writeSpoolChar(char c) throw(TransportException)
673 {
674 	if (fTransport) {
675 		fTransport->write(&c, 1);
676 	}
677 }
678 
679 
680 void
681 GraphicsDriver::rgb32_to_rgb24(void* src, void* dst, int width) {
682 	uint8* d = (uint8*)dst;
683 	rgb_color* s = (rgb_color*)src;
684 	for (int i = width; i > 0; i --) {
685 		*d ++ = s->red;
686 		*d ++ = s->green;
687 		*d ++ = s->blue;
688 		s++;
689 	}
690 }
691 
692 void
693 GraphicsDriver::cmap8_to_rgb24(void* src, void* dst, int width) {
694 	uint8* d = (uint8*)dst;
695 	uint8* s = (uint8*)src;
696 	const color_map* cmap = system_colors();
697 	for (int i = width; i > 0; i --) {
698 		const rgb_color* rgb = &cmap->color_list[*s];
699 		*d ++ = rgb->red;
700 		*d ++ = rgb->green;
701 		*d ++ = rgb->blue;
702 		s ++;
703 	}
704 }
705 
706 void
707 GraphicsDriver::convert_to_rgb24(void* src, void* dst, int width, color_space cs) {
708 	if (cs == B_RGB32) rgb32_to_rgb24(src, dst, width);
709 	else if (cs == B_CMAP8) cmap8_to_rgb24(src, dst, width);
710 	else {
711 		DBGMSG(("color_space %d not supported", cs));
712 	}
713 }
714 
715 uint8
716 GraphicsDriver::gray(uint8 r, uint8 g, uint8 b) {
717 	if (r == g && g == b) {
718 		return r;
719 	} else {
720 		return (r * 3 + g * 6 + b) / 10;
721 	}
722 }
723 
724 
725 void
726 GraphicsDriver::rgb32_to_gray(void* src, void* dst, int width) {
727 	uint8* d = (uint8*)dst;
728 	rgb_color* s = (rgb_color*)src;
729 	for (int i = width; i > 0; i --) {
730 		*d ++ = gray(s->red, s->green, s->blue);
731 		s++;
732 	}
733 }
734 
735 void
736 GraphicsDriver::cmap8_to_gray(void* src, void* dst, int width) {
737 	uint8* d = (uint8*)dst;
738 	uint8* s = (uint8*)src;
739 	const color_map* cmap = system_colors();
740 	for (int i = width; i > 0; i --) {
741 		const rgb_color* rgb = &cmap->color_list[*s];
742 		*d ++ = gray(rgb->red, rgb->green, rgb->blue);
743 		s ++;
744 	}
745 }
746 
747 void
748 GraphicsDriver::convert_to_gray(void* src, void* dst, int width, color_space cs) {
749 	if (cs == B_RGB32) rgb32_to_gray(src, dst, width);
750 	else if (cs == B_CMAP8) cmap8_to_gray(src, dst, width);
751 	else {
752 		DBGMSG(("color_space %d not supported", cs));
753 	}
754 }
755 
756