464 the result is a thresholded form, with white for |
473 the result is a thresholded form, with white for |
465 brightness values above 50%, black below" |
474 brightness values above 50%, black below" |
466 |
475 |
467 |monoBits f |
476 |monoBits f |
468 map rMap gMap bMap |
477 map rMap gMap bMap |
469 fast |
478 failed |
470 r g b |
479 r g b |
471 v "{ Class: SmallInteger }" |
480 v "{ Class: SmallInteger }" |
472 bitCount "{ Class: SmallInteger }" |
481 bitCount "{ Class: SmallInteger }" |
473 bits "{ Class: SmallInteger }" |
482 bits "{ Class: SmallInteger }" |
474 w "{ Class: SmallInteger }" |
483 w "{ Class: SmallInteger }" |
475 h "{ Class: SmallInteger }" |
484 h "{ Class: SmallInteger }" |
476 mapSize "{ Class: SmallInteger }" |
485 mapSize "{ Class: SmallInteger }" |
477 srcIndex "{ Class: SmallInteger }" |
486 srcIndex "{ Class: SmallInteger }" |
478 dstIndex "{ Class: SmallInteger }" | |
487 dstIndex "{ Class: SmallInteger }"| |
479 |
488 |
480 w := width. |
489 w := width. |
481 h := height. |
490 h := height. |
482 monoBits := ByteArray uninitializedNew:(((w + 7) // 8) * h). |
491 monoBits := ByteArray uninitializedNew:(((w + 7) // 8) * h). |
483 |
492 |
484 rMap := colorMap at:1. |
|
485 gMap := colorMap at:2. |
|
486 bMap := colorMap at:3. |
|
487 map := ByteArray uninitializedNew:256. |
493 map := ByteArray uninitializedNew:256. |
488 |
494 mapSize := colorMap size. |
489 mapSize := rMap size. |
495 |
|
496 " |
|
497 map entries: 0 for dark entries, 1 for bright entries |
|
498 " |
490 1 to:mapSize do:[:i | |
499 1 to:mapSize do:[:i | |
491 r := rMap at:i. |
500 map at:i put:(colorMap at:i) brightness rounded |
492 r notNil ifTrue:[ |
501 ]. |
493 g := gMap at:i. |
502 |
494 b := bMap at:i. |
503 failed := true. |
495 v := ((3 * r) + (6 * g) + (1 * b)) // 10. |
|
496 v := v bitShift:-7. "only keep hi-bit" |
|
497 (v == 1) ifTrue:[ |
|
498 map at:i put:0 "was: 1" |
|
499 ] ifFalse:[ |
|
500 map at:i put:1 "was: 0" |
|
501 ] |
|
502 ] |
|
503 ]. |
|
504 |
|
505 fast := false. |
|
506 %{ |
504 %{ |
507 register unsigned char *srcPtr, *dstPtr, *mapPtr; |
505 register unsigned char *srcPtr, *dstPtr, *mapPtr; |
508 register __v, __bits, __bitCount; |
506 register __v, __bits, __bitCount; |
509 register j; |
507 register j; |
510 register i; |
508 register i; |
511 extern OBJ ByteArray; |
509 extern OBJ ByteArray; |
512 |
510 |
513 if (__isByteArray(_INST(bytes)) && __isByteArray(map) && __isByteArray(monoBits)) { |
511 if (__isByteArray(_INST(bytes)) |
514 fast = true; |
512 && __isByteArray(map) |
|
513 && __isByteArray(monoBits)) { |
|
514 failed = false; |
515 srcPtr = _ByteArrayInstPtr(_INST(bytes))->ba_element; |
515 srcPtr = _ByteArrayInstPtr(_INST(bytes))->ba_element; |
516 dstPtr = _ByteArrayInstPtr(monoBits)->ba_element; |
516 dstPtr = _ByteArrayInstPtr(monoBits)->ba_element; |
517 mapPtr = _ByteArrayInstPtr(map)->ba_element; |
517 mapPtr = _ByteArrayInstPtr(map)->ba_element; |
518 for (i=_intVal(h); i>0; i--) { |
518 for (i=_intVal(h); i>0; i--) { |
519 __bitCount = 0; |
519 __bitCount = 0; |
681 x:0 y:0 |
689 x:0 y:0 |
682 into:(f id) x:0 y:0 width:w height:h with:(f gcId). |
690 into:(f id) x:0 y:0 width:w height:h with:(f gcId). |
683 ^ f |
691 ^ f |
684 ! |
692 ! |
685 |
693 |
686 paletteImageAsPseudoFormOn:aDevice |
694 paletteImageAsDitheredPseudoFormOn:aDevice |
687 "return a pseudoForm from the palette picture. The main work is |
695 "return a dithered pseudoForm from the palette picture. Depend |
688 in color reduction, when not all colors can be aquired." |
696 on dither colors being preallocated (see Color>>getColors*)" |
689 |
697 |
690 |pseudoBits f gcRound has8BitImage deviceDepth |
698 ^ self paletteImageAsDitheredPseudoFormOn:aDevice |
691 imgMap newImage pxl |
699 colors:Color fixColors |
692 usedColors usageCounts nUsed map mapIndex rMap gMap bMap |
700 nRed:Color numFixRed |
693 fit scale lastOK error |
701 nGreen:Color numFixGreen |
694 div |
702 nBlue:Color numFixBlue |
695 shift "{Class: SmallInteger }" |
703 ! |
696 m "{Class: SmallInteger }" | |
704 |
697 |
705 paletteImageAsDitheredPseudoFormOn:aDevice colors:fixColors nRed:nRed nGreen:nGreen nBlue:nBlue |
698 'D8Image: allocating colors ...' errorPrintNewline. |
706 "return a dithered pseudoForm from the palette picture. |
699 |
707 Use the colors in the fixColors array, which must be fixR x fixG x fixB |
700 "find used colors" |
708 colors assigned to aDevice, such as the preallocated colors of the |
701 |
709 Color class. |
702 usedColors := bytes usedValues. "gets us an array filled with used values" |
710 By passing the ditherColors as extra array, this method can |
703 "(could use bytes asBag)" |
711 also be used to dither an 8bit image into a smaller number of colors, |
704 nUsed := usedColors max + 1. |
712 for example to create Depth4Images." |
705 |
713 |
706 "sort by usage" |
714 "the code below is a q&d hack - it needs a rewrite to use a |
707 usageCounts := bytes usageCounts. |
715 floyd-steinberg dither (currently, the error is only forwarded |
708 usageCounts := usedColors asArray collect:[:clr | usageCounts at:(clr + 1)]. |
716 to the next pixel on the right, which seems ok for photograph-like |
709 usageCounts sort:[:a :b | a > b] with:usedColors. |
717 images, but leads to more artifacts with equal colored areas). |
710 |
718 " |
711 "allocate the colors (in order of usage count)" |
719 |
712 |
720 |pseudoBits f has8BitImage deviceDepth |
713 rMap := colorMap at:1. |
721 redBytes greenBytes blueBytes |
714 gMap := colorMap at:2. |
722 "/ eR "{Class: SmallInteger }" |
715 bMap := colorMap at:3. |
723 "/ eG "{Class: SmallInteger }" |
716 |
724 "/ eB "{Class: SmallInteger }" |
717 imgMap := Array new:nUsed. |
725 "/ wantR "{Class: SmallInteger }" |
718 |
726 "/ wantG "{Class: SmallInteger }" |
719 "first, we try to get the exact colors" |
727 "/ wantB "{Class: SmallInteger }" |
720 |
728 w "{Class: SmallInteger }" |
721 shift := (8 - aDevice bitsPerRGB) negated. |
729 h "{Class: SmallInteger }" |
722 m := (1 bitShift:(aDevice bitsPerRGB)) - 1. |
730 index "{Class: SmallInteger }" |
723 div := m asFloat. |
731 fixR "{Class: SmallInteger }" |
724 |
732 fixG "{Class: SmallInteger }" |
725 fit := true. |
733 fixB "{Class: SmallInteger }" |
726 scale := 100.0 / div. "to scale 0..255 into 0.0 .. 100.0" |
734 fixIds failed| |
727 lastOK := 0. |
735 |
728 usedColors do:[:aColorIndex | |
736 aDevice ~~ Display ifTrue:[^ nil]. |
729 |devColor color |
737 |
730 r "{Class: SmallInteger }" |
738 fixR := nRed. |
731 g "{Class: SmallInteger }" |
739 fixR == 0 ifTrue:[ ^ nil]. |
732 b "{Class: SmallInteger }" |
740 fixG := nGreen. |
733 rMask "{Class: SmallInteger }" |
741 fixG == 0 ifTrue:[ ^ nil]. |
734 gMask "{Class: SmallInteger }" |
742 fixB := nBlue. |
735 bMask "{Class: SmallInteger }"| |
743 fixB == 0 ifTrue:[ ^ nil]. |
736 |
744 "/ simple check |
737 fit ifTrue:[ |
745 (fixR * fixG * fixB) ~~ fixColors size ifTrue:[ |
738 gMask := bMask := rMask := m. |
746 self error:'invalid color array passed'. |
739 |
747 ^ nil |
740 mapIndex := aColorIndex + 1. |
748 ]. |
741 r := rMap at:mapIndex. |
749 fixIds := (fixColors asArray collect:[:clr | clr colorId]) asByteArray. |
742 g := gMap at:mapIndex. |
|
743 b := bMap at:mapIndex. |
|
744 color := Color red:((r bitShift:shift) bitAnd:rMask) * scale |
|
745 green:((g bitShift:shift) bitAnd:gMask) * scale |
|
746 blue:((b bitShift:shift) bitAnd:bMask) * scale. |
|
747 devColor := color exactOn:aDevice. |
|
748 devColor isNil ifTrue:[ |
|
749 "no such color - on the first round, do a GC to flush unused |
|
750 colors - this may help" |
|
751 gcRound == 0 ifTrue:[ |
|
752 ObjectMemory scavenge. |
|
753 devColor := color exactOn:aDevice. |
|
754 gcRound := 1 |
|
755 ]. |
|
756 devColor isNil ifTrue:[ |
|
757 gcRound == 1 ifTrue:[ |
|
758 'D8Image: force GC for possible color reclamation.' errorPrintNL. |
|
759 ObjectMemory markAndSweep. |
|
760 devColor := color exactOn:aDevice. |
|
761 gcRound := 2 |
|
762 ] |
|
763 ] |
|
764 ]. |
|
765 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
|
766 imgMap at:mapIndex put:devColor. |
|
767 lastOK := lastOK + 1. |
|
768 ] ifFalse:[ |
|
769 fit := false |
|
770 ] |
|
771 ] |
|
772 ]. |
|
773 |
|
774 "again, this time allow wrong colors (loop while increasing allowed error)" |
|
775 |
|
776 fit ifFalse:[ |
|
777 gcRound := 0. |
|
778 error := 10. |
|
779 [fit] whileFalse:[ |
|
780 fit := true. |
|
781 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
|
782 |devColor color |
|
783 r "{Class: SmallInteger }" |
|
784 g "{Class: SmallInteger }" |
|
785 b "{Class: SmallInteger }" |
|
786 rMask "{Class: SmallInteger }" |
|
787 gMask "{Class: SmallInteger }" |
|
788 bMask "{Class: SmallInteger }"| |
|
789 |
|
790 fit ifTrue:[ |
|
791 gMask := bMask := rMask := m. |
|
792 |
|
793 mapIndex := aColorIndex + 1. |
|
794 r := rMap at:mapIndex. |
|
795 g := gMap at:mapIndex. |
|
796 b := bMap at:mapIndex. |
|
797 color := Color red:((r bitShift:shift) bitAnd:rMask) * scale |
|
798 green:((g bitShift:shift) bitAnd:gMask) * scale |
|
799 blue:((b bitShift:shift) bitAnd:bMask) * scale. |
|
800 devColor := color nearestOn:aDevice error:error. |
|
801 devColor isNil ifTrue:[ |
|
802 "no such color - on the first round, do a GC to flush unused |
|
803 colors - this may help" |
|
804 gcRound == 0 ifTrue:[ |
|
805 ObjectMemory scavenge. |
|
806 devColor := color nearestOn:aDevice error:error. |
|
807 gcRound := 1 |
|
808 ]. |
|
809 devColor isNil ifTrue:[ |
|
810 gcRound == 1 ifTrue:[ |
|
811 'D8Image: force GC for possible color reclamation.' errorPrintNL. |
|
812 ObjectMemory markAndSweep. |
|
813 devColor := color nearestOn:aDevice error:error. |
|
814 gcRound := 2 |
|
815 ] |
|
816 ] |
|
817 ]. |
|
818 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
|
819 imgMap at:mapIndex put:devColor. |
|
820 lastOK := lastOK + 1. |
|
821 ] ifFalse:[ |
|
822 fit := false |
|
823 ] |
|
824 ]. |
|
825 ]. |
|
826 error := error * 2 |
|
827 ]. |
|
828 |
|
829 error > 100 ifTrue:[ |
|
830 'D8Image: not enough colors for a reasonable image' errorPrintNewline |
|
831 ] ifFalse:[ |
|
832 'D8Image: not enough colors for exact picture' errorPrintNewline. |
|
833 ] |
|
834 ]. |
|
835 |
|
836 "create translation map" |
|
837 map := ByteArray new:256. |
|
838 1 to:imgMap size do:[:i | |
|
839 (imgMap at:i) notNil ifTrue:[ |
|
840 map at:i put:(imgMap at:i) colorId |
|
841 ] |
|
842 ]. |
|
843 |
750 |
844 deviceDepth := aDevice depth. |
751 deviceDepth := aDevice depth. |
845 deviceDepth == 8 ifTrue:[ |
752 deviceDepth == 8 ifTrue:[ |
846 has8BitImage := true. |
753 has8BitImage := true. |
847 ] ifFalse:[ |
754 ] ifFalse:[ |
850 (fmt at:2) == 8 ifTrue:[ |
757 (fmt at:2) == 8 ifTrue:[ |
851 has8BitImage := true. |
758 has8BitImage := true. |
852 ] |
759 ] |
853 ] |
760 ] |
854 ]. |
761 ]. |
855 |
762 has8BitImage ifFalse:[^ nil]. |
|
763 |
|
764 'D8IMAGE: dithering ...' errorPrintNL. |
|
765 |
|
766 " |
|
767 collect color components as integer values |
|
768 (code below uses components percent * 2.55 asInteger everywhere, to avoid |
|
769 float arithmetic, rounding etc. Thus, the range is 0..255 here) |
|
770 " |
|
771 redBytes := ByteArray uninitializedNew:(colorMap size). |
|
772 greenBytes := ByteArray uninitializedNew:(colorMap size). |
|
773 blueBytes := ByteArray uninitializedNew:(colorMap size). |
|
774 1 to:(colorMap size) do:[:i | |
|
775 |clr| |
|
776 clr := colorMap at:i. |
|
777 redBytes at:i put:(clr red * 2.55) asInteger. |
|
778 greenBytes at:i put:(clr green * 2.55) asInteger. |
|
779 blueBytes at:i put:(clr blue * 2.55) asInteger. |
|
780 ]. |
|
781 |
|
782 pseudoBits := ByteArray uninitializedNew:(width * height). |
|
783 |
|
784 w := width. |
|
785 h := height. |
|
786 |
|
787 failed := true. |
|
788 |
|
789 %{ |
|
790 int __x, __y; |
|
791 int __eR, __eG, __eB; |
|
792 int __wantR, __wantG, __wantB; |
|
793 unsigned char *srcP, *dstP; |
|
794 unsigned char *redP, *greenP, *blueP; |
|
795 int pix; |
|
796 unsigned char *idP; |
|
797 int __fR, __fG, __fB; |
|
798 int iR, iG, iB; |
|
799 int idx; |
|
800 |
|
801 if (__isByteArray(_INST(bytes)) |
|
802 && __isByteArray(pseudoBits) |
|
803 && __isByteArray(redBytes) |
|
804 && __isByteArray(greenBytes) |
|
805 && __isByteArray(blueBytes) |
|
806 && __isByteArray(fixIds) |
|
807 && _isSmallInteger(fixR) |
|
808 && _isSmallInteger(fixG) |
|
809 && _isSmallInteger(fixB)) { |
|
810 failed = false; |
|
811 |
|
812 srcP = _ByteArrayInstPtr(_INST(bytes))->ba_element; |
|
813 dstP = _ByteArrayInstPtr(pseudoBits)->ba_element; |
|
814 redP = _ByteArrayInstPtr(redBytes)->ba_element; |
|
815 greenP = _ByteArrayInstPtr(greenBytes)->ba_element; |
|
816 blueP = _ByteArrayInstPtr(blueBytes)->ba_element; |
|
817 idP = _ByteArrayInstPtr(fixIds)->ba_element; |
|
818 __fR = _intVal(fixR)-1; |
|
819 __fG = _intVal(fixG)-1; |
|
820 __fB = _intVal(fixB)-1; |
|
821 |
|
822 for (__y=_intVal(h); __y>0; __y--) { |
|
823 __eR = __eG = __eB = 0; |
|
824 for (__x=_intVal(w); __x>0; __x--) { |
|
825 int t; |
|
826 int __want; |
|
827 |
|
828 pix = *srcP++; |
|
829 |
|
830 /* |
|
831 * wR, wG and wB is the wanted r/g/b value; |
|
832 * compute the index into the dId table .. |
|
833 * values: 0..255; scale to 0..fR-1, 0..fG-1, 0..fB-1 |
|
834 * |
|
835 * bad kludge: knows how to index into FixColor table |
|
836 */ |
|
837 __wantR = redP[pix] + __eR; |
|
838 __wantG = greenP[pix] + __eG; |
|
839 __wantB = blueP[pix] + __eB; |
|
840 |
|
841 if (__wantR > 255) __want = 255; |
|
842 else if (__wantR < 0) __want = 0; |
|
843 else __want = __wantR; |
|
844 |
|
845 iR = __want * __fR / 128; |
|
846 iR = (iR / 2) + (iR & 1); |
|
847 idx = iR * (__fG+1); |
|
848 |
|
849 if (__wantG > 255) __want = 255; |
|
850 else if (__wantG < 0) __want = 0; |
|
851 else __want = __wantG; |
|
852 |
|
853 iG = __want * __fG / 128; |
|
854 iG = (iG / 2) + (iG & 1); |
|
855 idx = (idx + iG) * (__fB+1); |
|
856 |
|
857 if (__wantB > 255) __want = 255; |
|
858 else if (__wantB < 0) __want = 0; |
|
859 else __want = __wantB; |
|
860 |
|
861 iB = __want * __fB / 128; |
|
862 iB = (iB / 2) + (iB & 1); |
|
863 idx = idx + iB; |
|
864 |
|
865 /* |
|
866 * store the corresponding dither colorId |
|
867 */ |
|
868 *dstP++ = idP[idx]; |
|
869 |
|
870 /* |
|
871 * the new error: |
|
872 */ |
|
873 __eR = __wantR - (iR * 256 / __fR); |
|
874 __eG = __wantG - (iG * 256 / __fG); |
|
875 __eB = __wantB - (iB * 256 / __fB); |
|
876 } |
|
877 } |
|
878 } |
|
879 %}. |
|
880 failed ifTrue:[ |
|
881 self primitiveFailed. |
|
882 ^ nil |
|
883 |
|
884 "/ for non-C programmers: |
|
885 "/ the above code is (roughly) equivalent to: |
|
886 "/ |
|
887 "/ index := 1. |
|
888 "/ 1 to:h do:[:y | |
|
889 "/ eR := eG := eB := 0. |
|
890 "/ 1 to:w do:[:x | |
|
891 "/ |pixel "{ Class: SmallInteger }" |
|
892 "/ clr |
|
893 "/ wR "{ Class: SmallInteger }" |
|
894 "/ wG "{ Class: SmallInteger }" |
|
895 "/ wB "{ Class: SmallInteger }" | |
|
896 "/ |
|
897 "/ pixel := (bytes at:index) + 1. |
|
898 "/ |
|
899 "/ wantR := ((redBytes at:pixel) + eR). |
|
900 "/ wantG := ((greenBytes at:pixel) + eG). |
|
901 "/ wantB := ((blueBytes at:pixel) + eB). |
|
902 "/ wR := wantR. |
|
903 "/ wR > 200 ifTrue:[wR := 200] ifFalse:[wR < 0 ifTrue:[wR := 0]]. |
|
904 "/ wG := wantG. |
|
905 "/ wG > 200 ifTrue:[wG := 200] ifFalse:[wG < 0 ifTrue:[wG := 0]]. |
|
906 "/ wB := wantB. |
|
907 "/ wB > 200 ifTrue:[wB := 200] ifFalse:[wB < 0 ifTrue:[wB := 0]]. |
|
908 "/ |
|
909 "/ iR := wR * (fixR-1) / 128. |
|
910 "/ iR := (iR / 2) + (iR bitAnd:1). |
|
911 "/ iG := wG * (fixG-1) / 128. |
|
912 "/ iG := (iG / 2) + (iG bitAnd:1). |
|
913 "/ iB := wB * (fixB-1) / 128. |
|
914 "/ iB := (iB / 2) + (iB bitAnd:1). |
|
915 "/ idx := (iR * fixR + iG) * fixB + iB. |
|
916 "/ clr := fixColors at:idx. |
|
917 "/ |
|
918 "/ eR := wantR - (clr red * 2) asInteger. |
|
919 "/ eG := wantG - (clr green * 2) asInteger. |
|
920 "/ eB := wantB - (clr blue * 2) asInteger. |
|
921 "/ |
|
922 "/ pixel := clr colorId. |
|
923 "/ pseudoBits at:index put:pixel. |
|
924 "/ |
|
925 "/ index := index + 1 |
|
926 "/ ]. |
|
927 "/ ]. |
|
928 ]. |
|
929 |
|
930 f := Form width:width height:height depth:deviceDepth on:aDevice. |
|
931 f isNil ifTrue:[^ nil]. |
|
932 f colorMap:fixColors. |
|
933 f initGC. |
|
934 aDevice drawBits:pseudoBits bitsPerPixel:8 depth:deviceDepth |
|
935 width:width height:height |
|
936 x:0 y:0 |
|
937 into:(f id) x:0 y:0 |
|
938 width:width height:height with:(f gcId). |
|
939 ^ f |
|
940 |
|
941 " |
|
942 example: color reduction from Depth8 to Depth4 can be done by: |
|
943 |
|
944 |img8 reducedImg8 img4 map| |
|
945 |
|
946 map := #( |
|
947 (0 0 0) |
|
948 (0 0 100) |
|
949 (0 50 0) |
|
950 (0 50 100) |
|
951 (0 100 0) |
|
952 (0 100 100) |
|
953 (100 0 0) |
|
954 (100 0 100) |
|
955 (100 50 0) |
|
956 (100 50 100) |
|
957 (100 100 0) |
|
958 (100 100 100)) collect:[:rgb | (Color red:(rgb at:1) |
|
959 green:(rgb at:2) |
|
960 blue:(rgb at:3)) on:Display]. |
|
961 |
|
962 img8 := Image fromFile:'bitmaps/bf.im8'. |
|
963 form := img8 paletteImageAsDitheredPseudoFormOn:Display |
|
964 colors:map |
|
965 nRed:2 |
|
966 nGreen:3 |
|
967 nBlue:2. |
|
968 img8 := Depth8Image fromForm:form. |
|
969 img4 := Depth4Image fromImage:img8. |
|
970 " |
|
971 ! |
|
972 |
|
973 paletteImageAsPseudoFormOn:aDevice |
|
974 "return a pseudoForm from the palette picture. The main work is |
|
975 in color reduction, when not all colors can be aquired." |
|
976 |
|
977 |pseudoBits f gcRound has8BitImage deviceDepth |
|
978 imgMap newImage pxl |
|
979 usedColors usageCounts nUsed map |
|
980 fit scale lastOK error |
|
981 div |
|
982 shift "{Class: SmallInteger }" |
|
983 m "{Class: SmallInteger }" | |
|
984 |
|
985 Color fixColors notNil ifTrue:[ |
|
986 f := self paletteImageAsDitheredPseudoFormOn:aDevice. |
|
987 f notNil ifTrue:[^ f]. |
|
988 ]. |
|
989 |
|
990 'D8IMAGE: allocating colors ...' errorPrintNL. |
|
991 |
|
992 "find used colors" |
|
993 |
|
994 usedColors := bytes usedValues. "gets us an array filled with used values" |
|
995 "(could use bytes asBag)" |
|
996 nUsed := usedColors max + 1. |
|
997 |
|
998 "sort by usage" |
|
999 usageCounts := bytes usageCounts. |
|
1000 usageCounts := usedColors asArray collect:[:clr | usageCounts at:(clr + 1)]. |
|
1001 usageCounts sort:[:a :b | a > b] with:usedColors. |
|
1002 |
|
1003 "allocate the colors (in order of usage count)" |
|
1004 |
|
1005 imgMap := Array new:nUsed. |
|
1006 |
|
1007 " |
|
1008 first, try to get the exact colors ... |
|
1009 " |
|
1010 shift := (8 - aDevice bitsPerRGB) negated. |
|
1011 m := (1 bitShift:(aDevice bitsPerRGB)) - 1. |
|
1012 div := m asFloat. |
|
1013 |
|
1014 fit := true. |
|
1015 scale := 100.0 / div. "to scale 0..255 into 0.0 .. 100.0" |
|
1016 lastOK := 0. |
|
1017 gcRound := 0. |
|
1018 |
|
1019 usedColors do:[:aColorIndex | |
|
1020 |devColor color |
|
1021 r "{Class: SmallInteger }" |
|
1022 g "{Class: SmallInteger }" |
|
1023 b "{Class: SmallInteger }" |
|
1024 mapIndex "{Class: SmallInteger }" |
|
1025 rMask "{Class: SmallInteger }" |
|
1026 gMask "{Class: SmallInteger }" |
|
1027 bMask "{Class: SmallInteger }"| |
|
1028 |
|
1029 fit ifTrue:[ |
|
1030 gMask := bMask := rMask := m. |
|
1031 |
|
1032 mapIndex := aColorIndex + 1. |
|
1033 color := colorMap at:mapIndex. |
|
1034 color colorId notNil ifTrue:[ |
|
1035 "wow - an immediate hit" |
|
1036 devColor := color |
|
1037 ] ifFalse:[ |
|
1038 devColor := color exactOn:aDevice. |
|
1039 devColor isNil ifTrue:[ |
|
1040 " |
|
1041 could not allocate color - on the first round, do a GC to flush |
|
1042 unused colors - this may help if some colors where locked by |
|
1043 already free images. |
|
1044 " |
|
1045 gcRound == 0 ifTrue:[ |
|
1046 ObjectMemory scavenge. |
|
1047 devColor := color exactOn:aDevice. |
|
1048 gcRound := 1 |
|
1049 ]. |
|
1050 devColor isNil ifTrue:[ |
|
1051 gcRound == 1 ifTrue:[ |
|
1052 'D8IMAGE: force GC for possible color reclamation.' errorPrintNL. |
|
1053 ObjectMemory markAndSweep. |
|
1054 devColor := color exactOn:aDevice. |
|
1055 gcRound := 2 |
|
1056 ] |
|
1057 ] |
|
1058 ]. |
|
1059 ]. |
|
1060 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
|
1061 imgMap at:mapIndex put:devColor. |
|
1062 lastOK := lastOK + 1. |
|
1063 ] ifFalse:[ |
|
1064 fit := false |
|
1065 ] |
|
1066 ] |
|
1067 ]. |
|
1068 |
|
1069 fit ifFalse:[ |
|
1070 " |
|
1071 again, this time allow wrong colors (loop while increasing allowed error) |
|
1072 " |
|
1073 error := 10. |
|
1074 [fit] whileFalse:[ |
|
1075 fit := true. |
|
1076 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
|
1077 |devColor color |
|
1078 r "{Class: SmallInteger }" |
|
1079 g "{Class: SmallInteger }" |
|
1080 b "{Class: SmallInteger }" |
|
1081 mapIndex "{Class: SmallInteger }" |
|
1082 rMask "{Class: SmallInteger }" |
|
1083 gMask "{Class: SmallInteger }" |
|
1084 bMask "{Class: SmallInteger }"| |
|
1085 |
|
1086 fit ifTrue:[ |
|
1087 gMask := bMask := rMask := m. |
|
1088 |
|
1089 mapIndex := aColorIndex + 1. |
|
1090 color := colorMap at:mapIndex. |
|
1091 r := (color red * 255 / 100) rounded. |
|
1092 g := (color green * 255 / 100) rounded. |
|
1093 b := (color blue * 255 / 100) rounded. |
|
1094 |
|
1095 color := Color red:((r bitShift:shift) bitAnd:rMask) * scale |
|
1096 green:((g bitShift:shift) bitAnd:gMask) * scale |
|
1097 blue:((b bitShift:shift) bitAnd:bMask) * scale. |
|
1098 |
|
1099 color colorId notNil ifTrue:[ |
|
1100 "wow - an immediate hit" |
|
1101 devColor := color |
|
1102 ] ifFalse:[ |
|
1103 devColor := color nearestOn:aDevice error:error. |
|
1104 devColor isNil ifTrue:[ |
|
1105 " |
|
1106 no free color - on the first round, do a GC to flush unused |
|
1107 colors - this may help if some colors where locked by already |
|
1108 free images. |
|
1109 " |
|
1110 gcRound == 0 ifTrue:[ |
|
1111 ObjectMemory scavenge. |
|
1112 devColor := color nearestOn:aDevice error:error. |
|
1113 gcRound := 1 |
|
1114 ]. |
|
1115 devColor isNil ifTrue:[ |
|
1116 gcRound == 1 ifTrue:[ |
|
1117 'D8IMAGE: force GC for possible color reclamation.' errorPrintNL. |
|
1118 ObjectMemory markAndSweep. |
|
1119 devColor := color nearestOn:aDevice error:error. |
|
1120 gcRound := 2 |
|
1121 ] |
|
1122 ] |
|
1123 ]. |
|
1124 ]. |
|
1125 (devColor notNil and:[devColor colorId notNil]) ifTrue:[ |
|
1126 imgMap at:mapIndex put:devColor. |
|
1127 lastOK := lastOK + 1. |
|
1128 ] ifFalse:[ |
|
1129 fit := false |
|
1130 ] |
|
1131 ]. |
|
1132 ]. |
|
1133 error := error * 2. |
|
1134 error > 1000 ifTrue:[ |
|
1135 " |
|
1136 break out, if the error becomes too big. |
|
1137 " |
|
1138 'D8IMAGE: hard color allocation problem - revert to b&w' errorPrintNL. |
|
1139 " |
|
1140 map to b&w as a last fallback. |
|
1141 (should really do a dither here) |
|
1142 " |
|
1143 usedColors from:(lastOK+1) to:(usedColors size) do:[:aColorIndex | |
|
1144 |color |
|
1145 mapIndex "{ Class: SmallInteger }"| |
|
1146 |
|
1147 mapIndex := aColorIndex + 1. |
|
1148 color := colorMap at:mapIndex. |
|
1149 color brightness > 0.5 ifTrue:[ |
|
1150 imgMap at:mapIndex put:(Color white on:aDevice). |
|
1151 ] ifFalse:[ |
|
1152 imgMap at:mapIndex put:(Color black on:aDevice). |
|
1153 ] |
|
1154 ]. |
|
1155 fit := true. |
|
1156 ] |
|
1157 ]. |
|
1158 |
|
1159 error > 100 ifTrue:[ |
|
1160 'D8IMAGE: not enough colors for a reasonable image' errorPrintNL |
|
1161 ] ifFalse:[ |
|
1162 'D8IMAGE: not enough colors for exact picture' errorPrintNL. |
|
1163 ] |
|
1164 ]. |
|
1165 |
|
1166 " |
|
1167 create translation map (from image colors to allocated colorIds) |
|
1168 " |
|
1169 map := ByteArray new:256. |
|
1170 1 to:imgMap size do:[:i | |
|
1171 (imgMap at:i) notNil ifTrue:[ |
|
1172 map at:i put:(imgMap at:i) colorId |
|
1173 ] |
|
1174 ]. |
|
1175 |
|
1176 deviceDepth := aDevice depth. |
|
1177 deviceDepth == 8 ifTrue:[ |
|
1178 has8BitImage := true. |
|
1179 ] ifFalse:[ |
|
1180 has8BitImage := false. |
|
1181 aDevice supportedImageFormats do:[:fmt | |
|
1182 (fmt at:2) == 8 ifTrue:[ |
|
1183 has8BitImage := true. |
|
1184 ] |
|
1185 ] |
|
1186 ]. |
|
1187 |
|
1188 " |
|
1189 finally, create a form on the device and copy (& translate) |
|
1190 the pixel values |
|
1191 " |
856 has8BitImage ifTrue:[ |
1192 has8BitImage ifTrue:[ |
857 pseudoBits := ByteArray uninitializedNew:(width * height). |
1193 pseudoBits := ByteArray uninitializedNew:(width * height). |
858 |
1194 |
859 bytes expandPixels:8 "xlate only" |
1195 bytes expandPixels:8 "xlate only" |
860 width:width |
1196 width:width |