330 scale := 100.0 / div. "to scale 0..255 into 0.0 .. 100.0" |
328 scale := 100.0 / div. "to scale 0..255 into 0.0 .. 100.0" |
331 lastOK := 0. |
329 lastOK := 0. |
332 gcRound := 0. |
330 gcRound := 0. |
333 |
331 |
334 usedColors do:[:aColorIndex | |
332 usedColors do:[:aColorIndex | |
335 |devColor color |
333 |devColor color |
336 r "{Class: SmallInteger }" |
334 r "{Class: SmallInteger }" |
337 g "{Class: SmallInteger }" |
335 g "{Class: SmallInteger }" |
338 b "{Class: SmallInteger }" |
336 b "{Class: SmallInteger }" |
339 mapIndex "{Class: SmallInteger }"| |
337 mapIndex "{Class: SmallInteger }"| |
340 |
338 |
341 fit ifTrue:[ |
339 fit ifTrue:[ |
342 mapIndex := aColorIndex + 1. |
340 mapIndex := aColorIndex + 1. |
343 "/ color := colorMap at:mapIndex. |
341 "/ color := colorMap at:mapIndex. |
344 |
342 |
345 color := self colorFromValue:aColorIndex. |
343 color := self colorFromValue:aColorIndex. |
346 (color isOnDevice:aDevice) ifTrue:[ |
344 (color isOnDevice:aDevice) ifTrue:[ |
347 "wow - an immediate hit" |
345 "wow - an immediate hit" |
348 devColor := color |
346 devColor := color |
349 ] ifFalse:[ |
347 ] ifFalse:[ |
350 devColor := color exactOn:aDevice. |
348 devColor := color exactOn:aDevice. |
351 devColor isNil ifTrue:[ |
349 devColor isNil ifTrue:[ |
352 " |
350 " |
353 could not allocate color - on the first round, do a GC to flush |
351 could not allocate color - on the first round, do a GC to flush |
354 unused colors - this may help if some colors where locked by |
352 unused colors - this may help if some colors where locked by |
355 already free images. |
353 already free images. |
356 " |
354 " |
357 gcRound == 0 ifTrue:[ |
355 gcRound == 0 ifTrue:[ |
358 ObjectMemory scavenge; finalize. |
356 ObjectMemory scavenge; finalize. |
359 devColor := color exactOn:aDevice. |
357 devColor := color exactOn:aDevice. |
360 gcRound := 1 |
358 gcRound := 1 |
361 ]. |
359 ]. |
362 devColor isNil ifTrue:[ |
360 devColor isNil ifTrue:[ |
363 gcRound == 1 ifTrue:[ |
361 gcRound == 1 ifTrue:[ |
364 CollectGarbageWhenRunningOutOfColors ifTrue:[ |
362 CollectGarbageWhenRunningOutOfColors ifTrue:[ |
365 'Depth8Image [info]: force GC for possible color reclamation.' infoPrintCR. |
363 'Depth8Image [info]: force GC for possible color reclamation.' infoPrintCR. |
366 ObjectMemory incrementalGC; finalize. |
364 ObjectMemory incrementalGC; finalize. |
367 devColor := color exactOn:aDevice. |
365 devColor := color exactOn:aDevice. |
368 ]. |
366 ]. |
369 gcRound := 2 |
367 gcRound := 2 |
370 ] |
368 ] |
371 ] |
369 ] |
372 ]. |
370 ]. |
373 ]. |
371 ]. |
374 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
372 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
375 imgMap at:mapIndex put:devColor. |
373 imgMap at:mapIndex put:devColor. |
376 lastOK := lastOK + 1. |
374 lastOK := lastOK + 1. |
377 ] ifFalse:[ |
375 ] ifFalse:[ |
378 fit := false |
376 fit := false |
379 ] |
377 ] |
380 ] |
378 ] |
381 ]. |
379 ]. |
382 |
380 |
383 fit ifFalse:[ |
381 fit ifFalse:[ |
384 ('Depth8Image [info]: got %1 exact colors (out of %2)' bindWith:lastOK with:usedColors size) infoPrintCR. |
382 ('Depth8Image [info]: got %1 exact colors (out of %2)' bindWith:lastOK with:usedColors size) infoPrintCR. |
385 |
383 |
386 DitherAlgorithm == #floydSteinberg ifTrue:[ |
384 DitherAlgorithm == #floydSteinberg ifTrue:[ |
387 dColors := imgMap collect:[:clr | clr isNil ifTrue:[clr] |
385 dColors := imgMap collect:[:clr | clr isNil ifTrue:[clr] |
388 ifFalse:[clr nearestOn:aDevice]]. |
386 ifFalse:[clr nearestOn:aDevice]]. |
389 dColors := dColors select:[:clr | clr notNil] thenCollect:[:clr | clr exactOn:aDevice]. |
387 dColors := dColors select:[:clr | clr notNil] thenCollect:[:clr | clr exactOn:aDevice]. |
390 dColors := dColors asSet. |
388 dColors := dColors asSet. |
391 dColors addAll:(aDevice colorMap collect:[:c|c onDevice:aDevice] thenSelect:[:c | c colorId notNil]). |
389 dColors addAll:(aDevice colorMap collect:[:c|c onDevice:aDevice] thenSelect:[:c | c colorId notNil]). |
392 ditherColors := aDevice availableDitherColors. |
390 ditherColors := aDevice availableDitherColors. |
393 ditherColors notNil ifTrue:[ |
391 ditherColors notNil ifTrue:[ |
394 dColors addAll:ditherColors. |
392 dColors addAll:ditherColors. |
395 ]. |
393 ]. |
396 dColors := dColors asArray. |
394 dColors := dColors asArray. |
397 dColors size > 256 ifTrue:[ |
395 dColors size > 256 ifTrue:[ |
398 dColors := dColors copyTo:256 |
396 dColors := dColors copyTo:256 |
399 ]. |
397 ]. |
400 ^ self asFloydSteinbergDitheredPseudoFormUsing:dColors on:aDevice |
398 ^ self asFloydSteinbergDitheredPseudoFormUsing:dColors on:aDevice |
401 ]. |
399 ]. |
402 |
400 |
403 " |
401 " |
404 again, this time allow wrong colors (loop while increasing allowed error) |
402 again, this time allow wrong colors (loop while increasing allowed error) |
405 " |
403 " |
406 error := 1. |
404 error := 1. |
407 [fit] whileFalse:[ |
405 [fit] whileFalse:[ |
408 fit := true. |
406 fit := true. |
409 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
407 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
410 |devColor color |
408 |devColor color |
411 r "{Class: SmallInteger }" |
409 r "{Class: SmallInteger }" |
412 g "{Class: SmallInteger }" |
410 g "{Class: SmallInteger }" |
413 b "{Class: SmallInteger }" |
411 b "{Class: SmallInteger }" |
414 mapIndex "{Class: SmallInteger }" |
412 mapIndex "{Class: SmallInteger }" |
415 rMask "{Class: SmallInteger }" |
413 rMask "{Class: SmallInteger }" |
416 gMask "{Class: SmallInteger }" |
414 gMask "{Class: SmallInteger }" |
417 bMask "{Class: SmallInteger }"| |
415 bMask "{Class: SmallInteger }"| |
418 |
416 |
419 fit ifTrue:[ |
417 fit ifTrue:[ |
420 gMask := bMask := rMask := m. |
418 gMask := bMask := rMask := m. |
421 |
419 |
422 mapIndex := aColorIndex + 1. |
420 mapIndex := aColorIndex + 1. |
423 "/ color := colorMap at:mapIndex. |
421 "/ color := colorMap at:mapIndex. |
424 color := self colorFromValue:aColorIndex. |
422 color := self colorFromValue:aColorIndex. |
425 r := (color red * 255 / 100.0) rounded. |
423 r := (color red * 255 / 100.0) rounded. |
426 g := (color green * 255 / 100.0) rounded. |
424 g := (color green * 255 / 100.0) rounded. |
427 b := (color blue * 255 / 100.0) rounded. |
425 b := (color blue * 255 / 100.0) rounded. |
428 |
426 |
429 color := Color red:((r bitShift:shift) bitAnd:rMask) * scale |
427 color := Color red:((r bitShift:shift) bitAnd:rMask) * scale |
430 green:((g bitShift:shift) bitAnd:gMask) * scale |
428 green:((g bitShift:shift) bitAnd:gMask) * scale |
431 blue:((b bitShift:shift) bitAnd:bMask) * scale. |
429 blue:((b bitShift:shift) bitAnd:bMask) * scale. |
432 |
430 |
433 (color isOnDevice:aDevice) ifTrue:[ |
431 (color isOnDevice:aDevice) ifTrue:[ |
434 "wow - an immediate hit" |
432 "wow - an immediate hit" |
435 devColor := color. |
433 devColor := color. |
436 ] ifFalse:[ |
434 ] ifFalse:[ |
437 devColor := color nearestOn:aDevice. |
435 devColor := color nearestOn:aDevice. |
438 (devColor notNil and:[(devColor deltaFrom:color) > error]) ifTrue:[ |
436 (devColor notNil and:[(devColor deltaFrom:color) > error]) ifTrue:[ |
439 devColor := nil |
437 devColor := nil |
440 ]. |
438 ]. |
441 devColor isNil ifTrue:[ |
439 devColor isNil ifTrue:[ |
442 " |
440 " |
443 no free color - on the first round, do a GC to flush unused |
441 no free color - on the first round, do a GC to flush unused |
444 colors - this may help if some colors where locked by already |
442 colors - this may help if some colors where locked by already |
445 free images. |
443 free images. |
446 " |
444 " |
447 gcRound == 0 ifTrue:[ |
445 gcRound == 0 ifTrue:[ |
448 ObjectMemory scavenge; finalize. |
446 ObjectMemory scavenge; finalize. |
449 devColor := color nearestOn:aDevice. |
447 devColor := color nearestOn:aDevice. |
450 (devColor notNil and:[(devColor deltaFrom:color) > error]) ifTrue:[ |
448 (devColor notNil and:[(devColor deltaFrom:color) > error]) ifTrue:[ |
451 devColor := nil |
449 devColor := nil |
452 ]. |
450 ]. |
453 gcRound := 1 |
451 gcRound := 1 |
454 ]. |
452 ]. |
455 devColor isNil ifTrue:[ |
453 devColor isNil ifTrue:[ |
456 gcRound == 1 ifTrue:[ |
454 gcRound == 1 ifTrue:[ |
457 CollectGarbageWhenRunningOutOfColors ifTrue:[ |
455 CollectGarbageWhenRunningOutOfColors ifTrue:[ |
458 'Depth8Image [info]: force GC for possible color reclamation.' infoPrintCR. |
456 'Depth8Image [info]: force GC for possible color reclamation.' infoPrintCR. |
459 ObjectMemory incrementalGC; finalize. |
457 ObjectMemory incrementalGC; finalize. |
460 devColor := color nearestOn:aDevice. |
458 devColor := color nearestOn:aDevice. |
461 (devColor notNil and:[(devColor deltaFrom:color) > error]) ifTrue:[ |
459 (devColor notNil and:[(devColor deltaFrom:color) > error]) ifTrue:[ |
462 devColor := nil |
460 devColor := nil |
463 ]. |
461 ]. |
464 ]. |
462 ]. |
465 gcRound := 2 |
463 gcRound := 2 |
466 ] |
464 ] |
467 ] |
465 ] |
468 ]. |
466 ]. |
469 ]. |
467 ]. |
470 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
468 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
471 imgMap at:mapIndex put:devColor. |
469 imgMap at:mapIndex put:devColor. |
472 lastOK := lastOK + 1. |
470 lastOK := lastOK + 1. |
473 ] ifFalse:[ |
471 ] ifFalse:[ |
474 fit := false |
472 fit := false |
475 ] |
473 ] |
476 ]. |
474 ]. |
477 ]. |
475 ]. |
478 |
476 |
479 fit ifTrue:[ |
477 fit ifTrue:[ |
480 ('Depth8Image [info]: remaining colors with error <= %1' bindWith:error) infoPrintCR. |
478 ('Depth8Image [info]: remaining colors with error <= %1' bindWith:error) infoPrintCR. |
481 ]. |
479 ]. |
482 |
480 |
483 error := error * 2. |
481 error := error * 2. |
484 error > 100 ifTrue:[ |
482 error > 100 ifTrue:[ |
485 " |
483 " |
486 break out, if the error becomes too big. |
484 break out, if the error becomes too big. |
487 " |
485 " |
488 'Depth8Image [info]: hard color allocation problem - revert to b&w for remaining colors' infoPrintCR. |
486 'Depth8Image [info]: hard color allocation problem - revert to b&w for remaining colors' infoPrintCR. |
489 " |
487 " |
490 map to b&w as a last fallback. |
488 map to b&w as a last fallback. |
491 (should really do a dither here) |
489 (should really do a dither here) |
492 " |
490 " |
493 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
491 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
494 |color |
492 |color |
495 mapIndex "{ Class: SmallInteger }"| |
493 mapIndex "{ Class: SmallInteger }"| |
496 |
494 |
497 mapIndex := aColorIndex + 1. |
495 mapIndex := aColorIndex + 1. |
498 "/ color := colorMap at:mapIndex. |
496 "/ color := colorMap at:mapIndex. |
499 color := self colorFromValue:aColorIndex. |
497 color := self colorFromValue:aColorIndex. |
500 color brightness > 0.4 ifTrue:[ |
498 color brightness > 0.4 ifTrue:[ |
501 color := Color white. |
499 color := Color white. |
502 ] ifFalse:[ |
500 ] ifFalse:[ |
503 color := Color black. |
501 color := Color black. |
504 ]. |
502 ]. |
505 imgMap at:mapIndex put:(color onDevice:aDevice). |
503 imgMap at:mapIndex put:(color onDevice:aDevice). |
506 ]. |
504 ]. |
507 fit := true. |
505 fit := true. |
508 ] |
506 ] |
509 ]. |
507 ]. |
510 |
508 |
511 error > 10 ifTrue:[ |
509 error > 10 ifTrue:[ |
512 'Depth8Image [info]: not enough colors for a reasonable image' infoPrintCR |
510 'Depth8Image [info]: not enough colors for a reasonable image' infoPrintCR |
513 ] ifFalse:[ |
511 ] ifFalse:[ |
514 'Depth8Image [info]: not enough colors for exact picture' infoPrintCR. |
512 'Depth8Image [info]: not enough colors for exact picture' infoPrintCR. |
515 ] |
513 ] |
516 ]. |
514 ]. |
517 |
515 |
518 " |
516 " |
519 create translation map (from image colors to allocated colorIds) |
517 create translation map (from image colors to allocated colorIds) |
520 " |
518 " |
521 mapSize := imgMap size. |
519 mapSize := imgMap size. |
522 map := ByteArray new:256. |
520 map := ByteArray new:256. |
523 1 to:mapSize do:[:i | |
521 1 to:mapSize do:[:i | |
524 (clr := imgMap at:i) notNil ifTrue:[ |
522 (clr := imgMap at:i) notNil ifTrue:[ |
525 map at:i put:clr colorId |
523 map at:i put:clr colorId |
526 ] |
524 ] |
527 ]. |
525 ]. |
528 |
526 |
529 " |
527 " |
530 does the device support 8-bit images ? |
528 does the device support 8-bit images ? |
531 " |
529 " |
532 deviceDepth := aDevice depth. |
530 deviceDepth := aDevice depth. |
533 has8BitImage := (deviceDepth == 8) |
531 has8BitImage := (deviceDepth == 8) |
534 or:[ (aDevice supportedImageFormatForDepth:8) notNil ]. |
532 or:[ (aDevice supportedImageFormatForDepth:8) notNil ]. |
535 |
533 |
536 " |
534 " |
537 finally, create a form on the device and copy (& translate) |
535 finally, create a form on the device and copy (& translate) |
538 the pixel values |
536 the pixel values |
539 " |
537 " |
540 has8BitImage ifTrue:[ |
538 has8BitImage ifTrue:[ |
541 pseudoBits := ByteArray uninitializedNew:(width * height). |
539 pseudoBits := ByteArray uninitializedNew:(width * height). |
542 |
540 |
543 bytes |
541 bytes |
544 expandPixels:8 "xlate only" |
542 expandPixels:8 "xlate only" |
545 width:width height:height |
543 width:width height:height |
546 into:pseudoBits |
544 into:pseudoBits |
547 mapping:map. |
545 mapping:map. |
548 |
546 |
549 map := nil. |
547 map := nil. |
550 |
548 |
551 f := Form width:width height:height depth:deviceDepth onDevice:aDevice. |
549 f := Form width:width height:height depth:deviceDepth onDevice:aDevice. |
552 f isNil ifTrue:[^ nil]. |
550 f isNil ifTrue:[^ nil]. |
553 f colorMap:imgMap. |
551 f colorMap:imgMap. |
554 f initGC. |
552 f initGC. |
555 aDevice |
553 aDevice |
556 drawBits:pseudoBits |
554 drawBits:pseudoBits |
557 bitsPerPixel:8 |
555 bitsPerPixel:8 |
558 depth:deviceDepth |
556 depth:deviceDepth |
559 padding:8 |
557 padding:8 |
560 width:width height:height |
558 width:width height:height |
561 x:0 y:0 |
559 x:0 y:0 |
562 into:(f id) x:0 y:0 |
560 into:(f id) x:0 y:0 |
563 width:width height:height |
561 width:width height:height |
564 with:(f gcId). |
562 with:(f gcId). |
565 ^ f |
563 ^ f |
566 ]. |
564 ]. |
567 |
565 |
568 " |
566 " |
569 slow fall back: convert into appropriate depth image, |
567 slow fall back: convert into appropriate depth image, |
570 by looping over each pixel individually |
568 by looping over each pixel individually |