xref: /haiku/src/libs/print/libprint/GraphicsDriver.cpp (revision b06a48ab8f30b45916a9c157b992827779182163)
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 *spool_file)
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 	spool_file->Seek(0, SEEK_SET);
83 	spool_file->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(spool_file);
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(spool_file);
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 *spool_file)
552 {
553 	bool success = true;
554 	if (!setupData(spool_file)) {
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(spool_file, 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 -> Quit();
585 		}
586 		cleanupBitmap();
587 		cleanupData();
588 	}
589 
590 	if (success == false) {
591 		BAlert *alert;
592 		if (fTransport->check_abort()) {
593 			alert = new BAlert("", fTransport->last_error().c_str(), "OK");
594 		} else {
595 			alert = new BAlert("", "Printer not responding.", "OK");
596 		}
597 		alert->Go();
598 	}
599 
600 	delete fTransport;
601 	fTransport = NULL;
602 
603 	return success;
604 }
605 
606 BMessage *
607 GraphicsDriver::takeJob(BFile* spool_file, uint32 flags)
608 {
609 	fFlags = flags;
610 	BMessage *msg;
611 	if (printJob(spool_file)) {
612 		msg = new BMessage('okok');
613 	} else {
614 		msg = new BMessage('baad');
615 	}
616 	return msg;
617 }
618 
619 bool
620 GraphicsDriver::startDoc()
621 {
622 	return true;
623 }
624 
625 bool
626 GraphicsDriver::startPage(int)
627 {
628 	return true;
629 }
630 
631 bool
632 GraphicsDriver::nextBand(BBitmap *, BPoint *)
633 {
634 	return true;
635 }
636 
637 bool
638 GraphicsDriver::endPage(int)
639 {
640 	return true;
641 }
642 
643 bool
644 GraphicsDriver::endDoc(bool)
645 {
646 	return true;
647 }
648 
649 void
650 GraphicsDriver::writeSpoolData(const void *buffer, size_t size) throw(TransportException)
651 {
652 	if (fTransport) {
653 		fTransport->write(buffer, size);
654 	}
655 }
656 
657 void
658 GraphicsDriver::writeSpoolString(const char *format, ...) throw(TransportException)
659 {
660 	if (fTransport) {
661 		char buffer[256];
662 		va_list	ap;
663 		va_start(ap, format);
664 		vsprintf(buffer, format, ap);
665 		fTransport->write(buffer, strlen(buffer));
666 		va_end(ap);
667 	}
668 }
669 
670 void
671 GraphicsDriver::writeSpoolChar(char c) throw(TransportException)
672 {
673 	if (fTransport) {
674 		fTransport->write(&c, 1);
675 	}
676 }
677 
678 
679 void
680 GraphicsDriver::rgb32_to_rgb24(void* src, void* dst, int width) {
681 	uint8* d = (uint8*)dst;
682 	rgb_color* s = (rgb_color*)src;
683 	for (int i = width; i > 0; i --) {
684 		*d ++ = s->red;
685 		*d ++ = s->green;
686 		*d ++ = s->blue;
687 		s++;
688 	}
689 }
690 
691 void
692 GraphicsDriver::cmap8_to_rgb24(void* src, void* dst, int width) {
693 	uint8* d = (uint8*)dst;
694 	uint8* s = (uint8*)src;
695 	const color_map* cmap = system_colors();
696 	for (int i = width; i > 0; i --) {
697 		const rgb_color* rgb = &cmap->color_list[*s];
698 		*d ++ = rgb->red;
699 		*d ++ = rgb->green;
700 		*d ++ = rgb->blue;
701 		s ++;
702 	}
703 }
704 
705 void
706 GraphicsDriver::convert_to_rgb24(void* src, void* dst, int width, color_space cs) {
707 	if (cs == B_RGB32) rgb32_to_rgb24(src, dst, width);
708 	else if (cs == B_CMAP8) cmap8_to_rgb24(src, dst, width);
709 	else {
710 		DBGMSG(("color_space %d not supported", cs));
711 	}
712 }
713 
714 uint8
715 GraphicsDriver::gray(uint8 r, uint8 g, uint8 b) {
716 	if (r == g && g == b) {
717 		return r;
718 	} else {
719 		return (r * 3 + g * 6 + b) / 10;
720 	}
721 }
722 
723 
724 void
725 GraphicsDriver::rgb32_to_gray(void* src, void* dst, int width) {
726 	uint8* d = (uint8*)dst;
727 	rgb_color* s = (rgb_color*)src;
728 	for (int i = width; i > 0; i --) {
729 		*d ++ = gray(s->red, s->green, s->blue);
730 		s++;
731 	}
732 }
733 
734 void
735 GraphicsDriver::cmap8_to_gray(void* src, void* dst, int width) {
736 	uint8* d = (uint8*)dst;
737 	uint8* s = (uint8*)src;
738 	const color_map* cmap = system_colors();
739 	for (int i = width; i > 0; i --) {
740 		const rgb_color* rgb = &cmap->color_list[*s];
741 		*d ++ = gray(rgb->red, rgb->green, rgb->blue);
742 		s ++;
743 	}
744 }
745 
746 void
747 GraphicsDriver::convert_to_gray(void* src, void* dst, int width, color_space cs) {
748 	if (cs == B_RGB32) rgb32_to_gray(src, dst, width);
749 	else if (cs == B_CMAP8) cmap8_to_gray(src, dst, width);
750 	else {
751 		DBGMSG(("color_space %d not supported", cs));
752 	}
753 }
754 
755