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