-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathImageProcessing.pck.st
8055 lines (7133 loc) · 364 KB
/
ImageProcessing.pck.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'From Cuis7.1 [latest update: #6511] on 9 July 2024 at 7:23:12 pm'!
'Description '!
!provides: 'ImageProcessing' 1 24!
!requires: 'Graphics-Files-Additional' 1 nil nil!
!requires: 'SignalProcessing' 1 100 nil!
!requires: 'ProbabilityDistributions' 1 nil nil!
!requires: 'Complex' 1 nil nil!
!requires: 'NumericalMethods' 1 8 nil!
!requires: 'Statistics' 1 nil nil!
SystemOrganization addCategory: #ImageProcessing!
SystemOrganization addCategory: #'ImageProcessing-Images'!
SystemOrganization addCategory: #'ImageProcessing-Interpolators'!
SystemOrganization addCategory: #'ImageProcessing-Obsolete'!
SystemOrganization addCategory: #'ImageProcessing-Support'!
SystemOrganization addCategory: #'ImageProcessing-Tests'!
!classDefinition: #FloatImage category: #'ImageProcessing-Images'!
FloatMatrix subclass: #FloatImage
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Images'!
!classDefinition: 'FloatImage class' category: #'ImageProcessing-Images'!
FloatImage class
instanceVariableNames: ''!
!classDefinition: #ConvolutionKernel category: #'ImageProcessing-Images'!
FloatImage subclass: #ConvolutionKernel
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Images'!
!classDefinition: 'ConvolutionKernel class' category: #'ImageProcessing-Images'!
ConvolutionKernel class
instanceVariableNames: ''!
!classDefinition: #IrregularSampledImage category: #'ImageProcessing-Images'!
FloatImage subclass: #IrregularSampledImage
instanceVariableNames: 'closestKnownX closestKnownY possibleTriPoints'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Images'!
!classDefinition: 'IrregularSampledImage class' category: #'ImageProcessing-Images'!
IrregularSampledImage class
instanceVariableNames: ''!
!classDefinition: #ComplexImage category: #'ImageProcessing-Images'!
Object subclass: #ComplexImage
instanceVariableNames: 'real imaginary'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Images'!
!classDefinition: 'ComplexImage class' category: #'ImageProcessing-Images'!
ComplexImage class
instanceVariableNames: ''!
!classDefinition: #MultiBandImage category: #'ImageProcessing-Images'!
Object subclass: #MultiBandImage
instanceVariableNames: 'bands'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Images'!
!classDefinition: 'MultiBandImage class' category: #'ImageProcessing-Images'!
MultiBandImage class
instanceVariableNames: ''!
!classDefinition: #GeneralizedInterpolatingImage category: #'ImageProcessing-Interpolators'!
Object subclass: #GeneralizedInterpolatingImage
instanceVariableNames: 'width height coefficients'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Interpolators'!
!classDefinition: 'GeneralizedInterpolatingImage class' category: #'ImageProcessing-Interpolators'!
GeneralizedInterpolatingImage class
instanceVariableNames: ''!
!classDefinition: #BSplineInterpolatingImage category: #'ImageProcessing-Interpolators'!
GeneralizedInterpolatingImage subclass: #BSplineInterpolatingImage
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Interpolators'!
!classDefinition: 'BSplineInterpolatingImage class' category: #'ImageProcessing-Interpolators'!
BSplineInterpolatingImage class
instanceVariableNames: ''!
!classDefinition: #ShiftedLinearInterpolatingImage category: #'ImageProcessing-Interpolators'!
GeneralizedInterpolatingImage subclass: #ShiftedLinearInterpolatingImage
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Interpolators'!
!classDefinition: 'ShiftedLinearInterpolatingImage class' category: #'ImageProcessing-Interpolators'!
ShiftedLinearInterpolatingImage class
instanceVariableNames: ''!
!classDefinition: #AccumulatedImageHistogram category: #'ImageProcessing-Obsolete'!
Object subclass: #AccumulatedImageHistogram
instanceVariableNames: 'tallies'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Obsolete'!
!classDefinition: 'AccumulatedImageHistogram class' category: #'ImageProcessing-Obsolete'!
AccumulatedImageHistogram class
instanceVariableNames: ''!
!classDefinition: #ImageHistogram category: #'ImageProcessing-Obsolete'!
Object subclass: #ImageHistogram
instanceVariableNames: 'tallies'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Obsolete'!
!classDefinition: 'ImageHistogram class' category: #'ImageProcessing-Obsolete'!
ImageHistogram class
instanceVariableNames: ''!
!classDefinition: #ImageHistogram2 category: #'ImageProcessing-Obsolete'!
Object subclass: #ImageHistogram2
instanceVariableNames: 'tallies'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Obsolete'!
!classDefinition: 'ImageHistogram2 class' category: #'ImageProcessing-Obsolete'!
ImageHistogram2 class
instanceVariableNames: ''!
!classDefinition: #ImageHistogram3 category: #'ImageProcessing-Obsolete'!
Object subclass: #ImageHistogram3
instanceVariableNames: 'tallies'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Obsolete'!
!classDefinition: 'ImageHistogram3 class' category: #'ImageProcessing-Obsolete'!
ImageHistogram3 class
instanceVariableNames: ''!
!classDefinition: #FFT2D category: #'ImageProcessing-Support'!
Object subclass: #FFT2D
instanceVariableNames: 'realMatrix imagMatrix'
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Support'!
!classDefinition: 'FFT2D class' category: #'ImageProcessing-Support'!
FFT2D class
instanceVariableNames: ''!
!classDefinition: #FloatImageTest category: #'ImageProcessing-Tests'!
TestCase subclass: #FloatImageTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ImageProcessing-Tests'!
!classDefinition: 'FloatImageTest class' category: #'ImageProcessing-Tests'!
FloatImageTest class
instanceVariableNames: ''!
!FloatImage commentStamp: '<historical>' prior: 0!
My instances are 1-band images suitable for Image Processing algorithms. Pixel values are 32-bit Float.
A glimpse of the things you can do:
| tiff jpg component iTiff iJpg diff |
tiff _ '../../4.2.04.tiff' asFileEntry form.
jpg _ '../../4.2.04.jpg' asFileEntry form.
jpg _ '../../4.2.04-95.jpg' asFileEntry form.
component _ #green.
iTiff _ FloatImage from32BitColorForm: tiff component: component.
iJpg _ FloatImage from32BitColorForm: jpg component: component.
diff _ iTiff - iJpg * 14 + 0.5.
diff histogram plot.
!
!ConvolutionKernel commentStamp: 'jmv 6/27/2018 16:01:07' prior: 0!
A ConvolutionKernel is a relatively small image with odd size in both directions. It is used to hold a PSF or a filter, and applied with convolution operations.
The sum of all elemente is 1.0
The origin (usually the largest element) is at the center. If used for CircularConvolution, adjustments are done as required.!
!IrregularSampledImage commentStamp: '<historical>' prior: 0!
Some real, known, pixels are added one by one (pixel position is usually random). At any point, all the other pixels can quickly be interpolated.
Allows for progressive reconstruction, with higher quality, as new data is available.
Can compute Digital Voronoi diagrams and Digital Delaunay Triangulation.!
!ComplexImage commentStamp: '<historical>' prior: 0!
My instances hold a real part and an imaginary part, which are FloatImage(s).!
!MultiBandImage commentStamp: 'jmv 7/23/2012 09:01' prior: 0!
a Multi Band Image is composed of several Single Band Images.
Each band can be used for a different spectral band (as in RGB color images, and multispectral and hyperspectral images). But a band can also mean panchromatic (black and white), opacity (translucent images), derived data such as image classification, or even extra data attached to each pixel by some user or application.
Each band can have a name (a Symbol) used to access it. It can also include a description, and maybe some arbitrary object for app-defined data.!
!GeneralizedInterpolatingImage commentStamp: '<historical>' prior: 0!
A kind of FloatImage that can interpolate values at any Float position (inside its bounds). Allows for "Generalized Interpolation" as described in:
P. Thevenaz, T. Blu, M. Unser, "Interpolation Revisited,"
IEEE Transactions on Medical Imaging,
vol. 19, no. 7, pp. 739-758, July 2000.
Available at: http://bigwww.epfl.ch/thevenaz/interpolation/
The basic idea is to use non-interpolating functions, meaning that instead of image samples, we store some pre-processed coefficients.
Subclasses implement specific pre-processing and interpolation.!
!BSplineInterpolatingImage commentStamp: '<historical>' prior: 0!
Cubic B-Spline Image Interpolation. Based on:
P. Thevenaz, T. Blu, M. Unser,
"Interpolation Revisited,"
IEEE Transactions on Medical Imaging,
vol. 19, no. 7, pp. 739-758, July 2000.
C code and paper are available at: http://bigwww.epfl.ch/thevenaz/interpolation/
I implement only the cubic case (degree=3), the most useful in practice.
The original C code does mirroring at the bounds. This version extends the edge values.
The original image was to be evaluated at integer indexes in (1 to: width) x (1 to height). My instance can be evaluated at any Float coordinates in (0.5 to: width+0.5) x (0.5 to: height+0.5). This is good for image zooming with proper bounds. See AffineTransformation>>#forImageZoom:!
!ShiftedLinearInterpolatingImage commentStamp: 'jmv 4/16/2013 13:40' prior: 0!
Shifted Linear Image Interpolation. Based on:
T. Blu, P. Thevenaz, M. Unser,
"Linear Interpolation Revitalized,"
IEEE Transactions on Image Processing,
vol. 13, no. 5, pp. 710-719, May 2004.
C code and paper are available at: http://bigwww.epfl.ch/thevenaz/shiftedlin/
The original image was to be evaluated at integer indexes in (1 to: width) x (1 to height). My instance can be evaluated at any Float coordinates in (0.5 to: width+0.5) x (0.5 to: height+0.5). This is good for image zooming with proper bounds.
Warning - Notes
--------------------
1) It looks like there is a slight displacement of rebuilt images. Because of this, I (jmv) chose to use basisShift/2 displacement on interpolation, instead of the original basisShift. See #showApparentDisplacement.
2) Generally, results are very good. The paper says SNR is good up to 3/4 Nyquist frequency. But in practice, images look very sharp, especially very fine textures. It appears that the lost high frequency content is replaced by good looking aliasing.
3) Successive interpolations are not really stable. The image value at the neighbourhood of some points seems to diverge to positive infinity, and for others, to negative infinity. This effect is even visible at the great Java demo at http://bigwww.epfl.ch/demo/jshiftlinear/start.php , if one choses 7 iterations. See FloatImage>>#testRotations .!
!AccumulatedImageHistogram commentStamp: 'jmv 6/4/2015 10:17' prior: 0!
Used by Histogram Equalization.
======================================================
ImageHistogram, ImageHistogram2, ImageHistogram3, AccumulatedImageHistogram
are now obsolete. They have been superseded by Histogram and UnitaryMonotonicMap
======================================================!
!ImageHistogram commentStamp: 'jmv 6/4/2015 10:16' prior: 0!
An ImageHistogram records the number of pixels in an image for each pixel value (actually for a number of pixel value intervals).
See ImageHistogram2 for a possibly better approach.
Pixel values are assumed to lie in the [0.0 .. 1.0] interval. Being N the size of the tallies array, there are N slots. N-2 slots have extent 1/(N-1). The two slots at the edges are [0.0 .. 1/(2N-2)) and [1 - (1/(2N-2)) .. 1.0].
For example, lets assume an ImageHistogram with 256 slots. If we store the values for a FloatImage built from a 8-bit gray bmp file, then the slot intervals would be:
[0.0/255 .. 0.5/255) "Counts pixels with original value 0, interval extent 0.5"
[0.5/255 .. 1.5/255) "Counts pixels with original value 1, interval extent 1"
[1.5/255 .. 2.5/255) "Counts pixels with original value 2, interval extent 1"
[2.5/255 .. 3.5/255) "Counts pixels with original value 3, interval extent 1"
[3.5/255 .. 4.5/255) "Counts pixels with original value 4, interval extent 1"
...
[252.5/255 .. 253.5/255) "Counts pixels with original value 253, interval extent 1"
[253.5/255 .. 254.5/255) "Counts pixels with original value 254, interval extent 1"
[254.5/255 .. 255/255] "Counts pixels with original value 255, interval extent 0.5"
These intervals, excluding the first and last, can be computed by:
| n |
n _ 256.
(2 to: n-1) collect: [ :i | (i-1/(n-1.0)-(1/(2*(n-1)))) to: (i-1/(n-1.0)+(1/(2*(n-1)))) count: 2 ].
In reality, the first and last slots will count also pixels with values less than 0.0 and greater than 1.0 respectively. I.e. they act as if the image was clipped. In this example, we can think that all slots have an interval of size 1, and that the first interval is [-0.5/255 .. 0.5/255) and that the last interval is [254.5/255 .. 255.5/255).
Each slot can count up to (2 raisedTo:32)-1 pixels. If it is ever needed to use larger counts, replace the WordArray by an Array initialized from zeros.
======================================================
ImageHistogram, ImageHistogram2, ImageHistogram3, AccumulatedImageHistogram
are now obsolete. They have been superseded by Histogram and UnitaryMonotonicMap
======================================================!
!ImageHistogram2 commentStamp: 'jmv 6/4/2015 10:16' prior: 0!
An ImageHistogram2 is a kind of image histogram, but it doesn't record exact pixel counts. They are especially useful for images with Float values, or when using significantly less histogram bins than pixel values.
Pixel values are assumed to lie in the [0.0 .. 1.0] interval. Being N the size of the tallies array, there are N slots. centered at values 0.0, 1/(N-1), 2/(N-1), ..., 1.0. A pixel with value exactly k/(N-1) will add 1.0 to the correspoinding tally. A pixel with value z such that k / (N-1) < z < (k+1) / (N-1) will contribute to 2 tallies, in inverse proportion to the distance to each of them.
In reality, the first and last slots will count also pixels with values less than 0.0 and greater than 1.0 respectively. I.e. they act as if the image was clipped.
This class had problems at the first and last bins when used for histogram equalization. The solution is ImageHistogran3 + AccumulatedImageHistogram
======================================================
ImageHistogram, ImageHistogram2, ImageHistogram3, AccumulatedImageHistogram
are now obsolete. They have been superseded by Histogram and UnitaryMonotonicMap
======================================================!
!ImageHistogram3 commentStamp: 'jmv 6/4/2015 10:16' prior: 0!
An ImageHistogram3 is a kind of image histogram, but it doesn't record exact pixel counts. They are especially useful for images with Float values, or when using significantly less histogram bins than pixel values.
Pixel values are assumed to lie in the [0.0 .. 1.0] interval. Being N the size of the tallies array, there are N slots or bins of equal width that span that interval. For example, for 5 bins:
[0.0 .. 0.2]
[0.2 .. 0.4]
[0.4 .. 0.6]
[0.6 .. 0.8]
[0.8 .. 1.0]
But more important is their center value. Bin i is centered at i-0.5 / N.
For example, for 5 bins:
(1 to: 5) collect: [ :i | i-0.5/5 ]
#(0.1 0.3 0.5 0.7 0.9)
A pixel with value exactly k-0.5/N will add 1.0 to the correspoinding tally. Pixels to the in the first half of the first bin belong in the first bin. Same for pixels in the second half of the last bin. Any other pixel value will contribute to 2 tallies, in inverse proportion to the distance to their center.
======================================================
ImageHistogram, ImageHistogram2, ImageHistogram3, AccumulatedImageHistogram
are now obsolete. They have been superseded by Histogram and UnitaryMonotonicMap
======================================================!
!FFT2D commentStamp: '<historical>' prior: 0!
My instances provide for 2D FFT on FloatMatrix'es and FloatImages. I rely on FFT for succesive transform by rows and by columns.!
!FloatImage methodsFor: 'arithmetic-answer new' stamp: 'jmv 7/5/2018 07:27:09'!
* aFloatImageOrNumber
"Answer a new FloatImage. Don't affect receiver or argument in any way.
Element by element product."
^self copy *= aFloatImageOrNumber! !
!FloatImage methodsFor: 'arithmetic-answer new' stamp: 'jmv 7/5/2018 07:28:10'!
/ aFloatImageOrNumber
"Answer a new FloatImage. Don't affect receiver or argument in any way.
Element by element division."
^self copy /= aFloatImageOrNumber! !
!FloatImage methodsFor: 'arithmetic-answer new' stamp: 'jmv 8/29/2014 10:58'!
sqrt
^self copy replaceValues: [ :i :j :v | v sqrt ]! !
!FloatImage methodsFor: 'arithmetic-answer new' stamp: 'jmv 8/3/2012 15:14'!
squared
"Answer the receiver multipled by itself."
^self * self! !
!FloatImage methodsFor: 'arithmetic-modify self' stamp: 'jmv 8/23/2014 23:37'!
*= aFloatImageOrNumber
"Multiply receiver by an image (pixel by pixel) or number"
aFloatImageOrNumber isNumber
ifTrue: [ elements *= aFloatImageOrNumber ]
ifFalse: [
self validateSameSize: aFloatImageOrNumber.
elements *= aFloatImageOrNumber elements ]! !
!FloatImage methodsFor: 'arithmetic-modify self' stamp: 'jmv 7/2/2019 08:53:18'!
/= aFloatImageOrNumber
"Divide receiver by an image (pixel by pixel) or number"
^self divideBy: aFloatImageOrNumber
ifDivisorZero: [ZeroDivide new signalReceiver: self selector: #/= argument: aFloatImageOrNumber]
ifBothZero: [ZeroDivide new signalReceiver: self selector: #/= argument: aFloatImageOrNumber]! !
!FloatImage methodsFor: 'arithmetic-modify self' stamp: 'jmv 7/8/2018 18:30:56'!
divideBy: aFloatImageOrNumber ifDivisorZero: zeroDivisionBlockOrValue ifBothZero: indeterminateBlockOrValue
"Divide receiver by an image (pixel by pixel) or number"
aFloatImageOrNumber isNumber
ifTrue: [ elements divideBy: aFloatImageOrNumber ifDivisorZero: zeroDivisionBlockOrValue ifBothZero: indeterminateBlockOrValue]
ifFalse: [
self validateSameSize: aFloatImageOrNumber.
elements divideBy: aFloatImageOrNumber elements ifDivisorZero: zeroDivisionBlockOrValue ifBothZero: indeterminateBlockOrValue ]! !
!FloatImage methodsFor: 'resampling-basic' stamp: 'jmv 6/12/2024 15:00:21'!
scaledBy: scale
"Answer a new image. Extent is multiplied by scale (i.e. scale=0.5 means half size, scale=2 means twice size.
Gives very good results for any scale values, including very large and very small.
Uses unfiltered BSpline interpolation for large scale factors, and Lanczos filtering and resampling for small scale factors.
But you can use the methods specific to each technique if you know what you want.
| original |
original _ FloatImage checkerboard: 100@100 n: 50.
(original scaledBy: 4.0) display.
original display.
(original scaledBy: 0.25) display.
| original |
original _ FloatImage fromForm: Cursor readCursor asGrayForm.
(original scaledBy: 4.0) display.
original asForm displayAt: 16@0.
(original scaledBy: 0.7) display.
"
| t |
t := AffineTransformation forImageZoom: scale.
^scale < 0.8
ifTrue: [
scale < 0.25
ifTrue: [self areaTransformedBy: t resultExtent: nil]
ifFalse: [self lanczosTransformedBy: t resultExtent: nil]]
ifFalse: [self bSplineTransformedBy: t resultExtent: nil ]! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 7/20/2018 11:27:33'!
areaTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Apply a pixel area filtering for interpolation, akin to OpenCV's CV_INTER_AREA.
Very fast. Very good for strong zoom out (like building image thumbnails). Essentially the same as Same as #nearestNeighborTransformedBy:resultExtent: for scale factors >=1 (zoom in)
"
| scaled resultHeight resultWidth p v hh hw inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
inverseTransformation _ anAffineTransformation inverseTransformation.
hw _ 0.5 * width / resultWidth max: 0.5.
hh _ 0.5 * height / resultHeight max: 0.5.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
v _ self areaFilteredValueAtX: p x y: p y halfWidth: hw halfHeight: hh.
scaled x: x y: y put: v
]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 5/22/2015 12:25'!
bSplineTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
See comment at BSplineInterpolatingImage. Not available in OpenCV.
Gives top quality results for large scale values (not much smaller than 1.0)
In such cases, prefer a filtering technique such as Area or Lanczos to avoid aliasing.
"
| scaled interpolator resultHeight resultWidth p v inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
interpolator _ BSplineInterpolatingImage from: self.
inverseTransformation _ anAffineTransformation inverseTransformation.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
v _ interpolator interpolatedValueAtX: p x y: p y.
scaled x: x y: y put: v ]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 9/3/2020 17:12:44'!
bicubicTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Evaluate a bicubic (i.e. good quality) interpolation. Like OpenCV's CV_INTER_CUBIC
"
| scaled resultHeight resultWidth p v xFilter inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
inverseTransformation _ anAffineTransformation inverseTransformation.
xFilter _ Float32Array new: 4.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
v _ self bicubicInterpolatedValueAtX: p x y: p y xFilterStore: xFilter.
scaled x: x y: y put: v
]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 5/22/2015 12:25'!
bilinearTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Evaluate a bilinear (i.e. cheap) interpolation.
Like OpenCV's CV_INTER_LINEAR"
| scaled resultHeight resultWidth p v inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
inverseTransformation _ anAffineTransformation inverseTransformation.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
v _ self bilinearInterpolatedValueAtX: p x y: p y.
scaled x: x y: y put: v
]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 7/19/2018 16:09:01'!
fourierDoubledBilinearTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Evaluate a bilinear interpolation on a reasonably good upscaled version of the receiver.
Result is very sharp, but with a bit of high frequency ripple.
Not very practical, but instructive. Results are very similar to fourierPhaseDisplacedBy: that is also very instructive."
| fftExtent complex biggerExtent biggerComplex
e resultWidth resultHeight scaled inverseTransformation p v totalScale |
fftExtent _ 2 raisedToInteger: ((width max: height) log: 2) ceiling.
complex _ ComplexImage extent: fftExtent.
complex realPart fillWith: self.
complex fftTransform: true.
biggerExtent _ 2 raisedToInteger: ((width max: height) log: 2) ceiling + 1.
biggerComplex _ ComplexImage extent: biggerExtent.
biggerComplex fillLowFFTFrequenciesWith: complex.
biggerComplex *= 4.
biggerComplex fftTransform: false.
totalScale _ biggerExtent / width.
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
inverseTransformation _ anAffineTransformation inverseTransformation.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
p _ p * totalScale.
v _ biggerComplex realPart bilinearInterpolatedValueAtX: p x-1 y: p y-1.
scaled x: x y: y put: v
]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 7/19/2018 16:14:52'!
fourierPhaseDisplacedBy: delta
"Answer a new image.
See #fourierPhaseDisplaceBy:
Not very practical, but instructive. Results are very similar to #fourierDoubledBilinearTransformedBy:resultExtent: that is also very instructive.
FloatImage lena fourierPhaseDisplacedBy: 0.4@0 :: displayAt: 0@0 zoom: 4
"
| fftExtent complex answer |
fftExtent _ 2 raisedToInteger: ((width max: height) log: 2) ceiling.
complex _ ComplexImage extent: fftExtent.
complex realPart fillWith: self.
complex fftTransform: true.
complex fourierPhaseDisplaceBy: delta.
complex fftTransform: false.
fftExtent = self extent
ifTrue: [ answer _ complex realPart ]
ifFalse: [
answer _ FloatImage extent: self extent.
answer fillWith: complex realPart ].
^answer! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 9/3/2020 17:12:45'!
lanczosTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Note about OpenCV's INTER_LANCZOS4:
According to online documentation, It looks like OpenCV a Lanczos filter of fixed support.
That means that the filter is fitted to the sampling frequency of the source image.
So, no real filtering is done, just interpolation. Aliasing is expected when zooming out!!
http://docs.opencv.org/modules/imgproc/doc/geometric_transformations.html#resize
This method of image resampling is very good for scale < 0.9, because of good filtering.
Even in these cases, Area filtering might be preferred, because it is quite good and much faster.
For scale close to 1.0 and greater, no filtering is needed. In such cases a good interpolation
like #bSplineTransformedBy:resultExtent: could give better results, and better performance
(especially if many resamplings will be done on the same source image).
"
| scaled resultHeight resultWidth p filterScale n xFilter yFilter v inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
inverseTransformation _ anAffineTransformation inverseTransformation.
"If downscaling (zooming out, filterScale<1, subsampling) remove frequencies above sf, as they would become aliasing in the result.
If upscaling (zooming in, filterScale > 1), avoid creating frequencies above 1 in the result, as they were not in ourselves."
filterScale _ ((resultWidth / width) min: (resultHeight / height)) min: 1.0.
n _ (4 / filterScale) floor +1.
xFilter _ Float32Array new: n.
yFilter _ Float32Array new: n.
1 to: resultHeight do: [ :y |
1 to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
v _ self lanczosFilteredValueAtX: p x y: p y filterScale: filterScale xFilterStore: xFilter yFilterStore: yFilter.
scaled x: x y: y put: v ]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 5/22/2015 12:27'!
nearestNeighborTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Evaluate a Nearest Neighbor (i.e. very cheap, low quality) interpolation.
Like OpenCV's CV_INTER_NN
"
| scaled resultHeight resultWidth p v inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
inverseTransformation _ anAffineTransformation inverseTransformation.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
"Protect against invalid access"
(p >= (0.5@0.5) and: [ p < (width@height + 0.5) ]) ifTrue: [
v _ self x: p x rounded y: p y rounded.
scaled x: x y: y put: v
]
]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 8/9/2018 11:22:50'!
perspectiveTransformTopLeft: topLeft topRight: topRight bottomLeft: bottomLeft bottomRight: bottomRight
"Resample the receiver to a new Float image.
The arguments specify which point in the receiver maps to each corner of the result.
The arguments might be Integer or Float points.
Apply a PerspectiveTransformation"
"
| i p r |
i _ FloatImage lena.
p _ i perspectiveTransformTopLeft: -800@1 topRight: 256+800@1bottomLeft: 1@256 bottomRight: 256@256.
r _ p perspectiveTransformTopLeft: 111.1+0@1 topRight: 146+0.1@1bottomLeft: 1@256 bottomRight: 256@256.
i displayAt: 0@0.
p displayAt: 256@0.
r displayAt: 512@0.
"
| scaled resultHeight resultWidth p v cornersInSource transformation resultCorners interpolator |
cornersInSource _ {topLeft. topRight. bottomLeft. bottomRight }.
resultWidth _ width.
resultHeight _ height.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
resultCorners _ { 1@1. resultWidth@1. 1@resultHeight. resultWidth@resultHeight }.
transformation _ Homography map: resultCorners to: cornersInSource.
interpolator _ BSplineInterpolatingImage from: self.
1 to: resultHeight do: [ :y |
1 to: resultWidth do: [ :x |
p _ transformation map: x@y.
v _ interpolator interpolatedValueAtX: p x y: p y.
scaled x: x y: y put: v ]].
^scaled! !
!FloatImage methodsFor: 'resampling-advanced' stamp: 'jmv 5/22/2015 12:27'!
shiftedLinearTransformedBy: anAffineTransformation resultExtent: resultExtentOrNil
"Answer a new image.
anAffineTransformation specifies the transformation from us to the result image.
resultExtentOrNil ifNil: [ make it large enough ]
Warning: there could be black areas in the result, and/or some image content in self could be lost.
This depends on the arguments.
Gives very good results for scale values not much smaller than 1.0. In such cases, prefer a filtering technique such
as #lanczosTransformedBy:resultExtent: to avoid aliasing.
See ShiftedLinearInterpolatingImage class comment.
Not available in OpenCV.
"
| scaled interpolator resultHeight resultWidth p v inverseTransformation e |
e _ resultExtentOrNil ifNil: [ self resultExtentFor: anAffineTransformation ].
resultWidth _ e x.
resultHeight _ e y.
scaled _ self appropriateResultClass width: resultWidth height: resultHeight.
interpolator _ ShiftedLinearInterpolatingImage from: self.
inverseTransformation _ anAffineTransformation inverseTransformation.
1 to: resultHeight do: [ :y |
1to: resultWidth do: [ :x |
p _ inverseTransformation transform: x@y.
v _ interpolator interpolatedValueAtX: p x y: p y.
scaled x: x y: y put: v ]].
^scaled! !
!FloatImage methodsFor: 'noise generation' stamp: 'jmv 7/19/2016 12:08:32'!
addNormalNoise: standardDeviation
| random |
random _ NormalProbabilityDistribution mean: 0.0 standardDeviation: standardDeviation.
self replaceValues: [ :i :j :v | random nextRandomNumber + v ]! !
!FloatImage methodsFor: 'noise generation' stamp: 'jmv 6/25/2018 15:27:20'!
simulatePhotonNoise
"Simulate Photon Noise, assuming we count electrons (i.e. photons converted into electrons by an image sensor).
Good approximation of Poisson noise by a Normal noise (proportional to square root of electron count).
Example of use:"
"
| image sensorPixelFullWell electronsWithSimulatedPhotonNoise imageWithNoise |
image := FloatImage lena. 'element values in [0.0 .. 1.0]'.
sensorPixelFullWell := 500. 'electrons, as per sensor spec'.
electronsWithSimulatedPhotonNoise := image * sensorPixelFullWell. 'Make it count electrons (no prob if not integer)'.
electronsWithSimulatedPhotonNoise simulatePhotonNoise. 'Add simulated noise'.
imageWithNoise := electronsWithSimulatedPhotonNoise / sensorPixelFullWell. 'Convert back to [0.0 .. 1.0]'.
imageWithNoise display
"
| random |
random _ NormalProbabilityDistribution new.
self replaceValues: [ :i :j :v |
(v max: 0) sqrt * random nextRandomNumber + v max: 0 ]! !
!FloatImage methodsFor: 'noise generation' stamp: 'jmv 6/25/2018 10:31:31'!
simulateReadNoise: electrons
"Simulate Read Noise, assuming we count electrons (i.e. photons converted into electrons by an image sensor).
Example of use:"
"
| image sensorPixelFullWell sensorReadNoise electronsWithSimulatedReadNoise imageWithNoise |
image := FloatImage lena. 'element values in [0.0 .. 1.0]'.
sensorPixelFullWell := 500. 'electrons, as per sensor spec'.
sensorReadNoise := 20. 'electrons, as per sensor spec'.
electronsWithSimulatedReadNoise := image * sensorPixelFullWell. 'Make it count electrons (no prob if not integer)'.
electronsWithSimulatedReadNoise simulateReadNoise: sensorReadNoise. 'Add simulated noise'.
imageWithNoise := electronsWithSimulatedReadNoise / sensorPixelFullWell. 'Convert back to [0.0 .. 1.0]'.
imageWithNoise display
"
self addNormalNoise: electrons! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 6/3/2015 21:49'!
applyMap: map
self replaceValues: [ :i :j :v | map valueAt: (self i: i j: j)]! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 6/3/2015 22:20'!
applyMapInverse: map
self replaceValues: [ :i :j :v | map inverseValueAt: (self i: i j: j)]! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 6/4/2015 10:56'!
ditherForHistogramEq
"Add some uniform random noise so image values are distributed over the entire real [0.0 .. 1.0] interval.
This is good, for example, for histogram equalization, as it allows getting a really flat histogram after equalization,
covering esentially all possible values.
Assume 256 levels, i.e. an image built from an 8bpp Form or file.
Tweak as needed, etc."
| k random levelsInOriginalImage |
levelsInOriginalImage _ 256.
k _ 1.0 / (levelsInOriginalImage-1).
random _ Random new.
self replaceValues: [ :i :j :v | v + (random next - 0.5*k) min: 1 max: 0 ].! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 8/9/2018 12:47:57'!
gammaEncodedToLinearSpace
"Assume the receiver is in gamma encoded space (values are proportional luminance, framebuffer contents).
Modify it so values are proportional to photon count (intensity of light, radiance flux).
Useful, for example, for taking an image read from a jpg or png file (gamma encoded)
and preparing it for processing in the linear space.
Results are meaningful if values in [0.0 .. 1.0]
"
self replaceValues: [ :i :j :v | Color sRGBGammaToLinear: v ]! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 5/24/2018 09:00:42'!
histogramEq
"Could include random noise to distribute pixels over all possible values... Dithering...
To do this properly we'd need to know more about distribution of values. For instance, if an 8-bit image, just use a 256 bins histogram and dither.
But if the image is really Float, then do nothing!!
FloatImage lena histogramEq; displayAt: 0@0.
FloatImage lena ditherForHistogramEq; histogramEq; displayAt: 256@0.
(FloatImage lena histogramEq; histogram) plot.
(FloatImage lena ditherForHistogramEq; histogramEq; histogram) plot.
"
self applyMap: self histogramUnitRange accumulatedAsMap! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 8/9/2018 12:48:06'!
linearSpaceToGammaEncoded
"Assume the receiver is in linear space (values are proportional to photon count).
Modify it so values are proportional to human perceived brightness.
Useful, for example, for displaying:
aFloatImageInLinearSpace copy linearSpaceToGammaEncoded displayAt: 0@0
Results are meaningful if values in [0.0 .. 1.0]
"
self replaceValues: [ :i :j :v | Color linearTosRGBGamma: v ]! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 8/23/2014 23:46'!
normalizeValues
"Make min pixel value 0.0, and max pixel value 1.0.
Adjust pixel values proportionally"
elements -= elements min.
elements *= (1.0 / elements max)! !
!FloatImage methodsFor: 'dynamic range operations' stamp: 'jmv 8/23/2014 23:41'!
normalizeValuesFloor: min ceiling: max
"Make argument min pixel value 0.0, and argument max pixel value 1.0.
Adjust pixel values proportionally. Clamp values outside."
self replaceValues: [ :i :j :v | v - min / max min: 1.0 max: 0.0 ]! !
!FloatImage methodsFor: 'interpolation' stamp: 'jmv 8/14/2018 17:28:21'!
areaFilteredValueAtX: x y: y halfWidth: hw halfHeight: hh
"Answer an interpolated value at (most likely) non-integer x@y.
x and y can be any Float values in in [1.0 .. width] and [1.0 .. height].
Akin to OpenCV's CV_INTER_AREA.
Applies a step filter over the samples in the receiver that lie in a rectangle centered at x@y, as specified by hw and hh.
Note: We could also consider the pixels in the receiver as squares and compute area intersection with result pixel.
Results would be slightly better. But here we are focused on speed. If you need higher quality, please use Lanczos.
Computationally very cheap.
Avoids aliasing while reasonably keeps detail.
Gives very good results for strong subsampling (zoom out, scale factors less than 0.4).
Compute parameters as (scale usually <= 0.25):
hw _ 1 / scale / 2.0.
hh _ 1 / scale / 2.0.
or (resultWidth usually much smaller than original, same for height):
hw _ originalWidth / resultWidth / 2.0.
hh _ originalHeight / resultHeight / 2.0.
"
| xStart xStop yStart yStop sum xIndex0 yIndex0 |
"Validate inside some reasonable bounds. We allow extending up to 0.8 in any direction"
xIndex0 _ x floor - 1.
"x < 0.2 ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
xIndex0 < -1 ifTrue: [ ^0.0 ].
(xIndex0 = -1 and: [x < 0.2]) ifTrue: [ ^0.0 ].
"x > (width + 0.8) ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
xIndex0 > width ifTrue: [ ^0.0 ].
(xIndex0 = width and: [x > (width + 0.8)]) ifTrue: [ ^0.0 ].
yIndex0 _ y floor - 1.
"y < 0.2 ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
yIndex0 < -1 ifTrue: [ ^0.0 ].
(yIndex0 = -1 and: [y < 0.2]) ifTrue: [ ^0.0 ].
"y > (height + 0.8) ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
yIndex0 > height ifTrue: [ ^0.0 ].
(yIndex0 = height and: [y > (height + 0.8)]) ifTrue: [ ^0.0 ].
xStart _ (x - hw) rounded max: 1.
xStop _ (x + hw) rounded min: width.
yStart _ (y - hh) rounded max: 1.
yStop _ (y + hh) rounded min: height.
"Apply filter."
sum _ 0.0.
yStart to: yStop do: [ :yyy |
xStart to: xStop do: [ :xxx |
sum _ (self x: xxx y: yyy) + sum ]].
sum = 0.0 ifTrue: [ ^0.0 ].
^sum / ((xStop-xStart+1) * (yStop-yStart+1))! !
!FloatImage methodsFor: 'interpolation' stamp: 'jmv 5/4/2015 13:44'!
bicubicInterpolatedValueAtX: x y: y xFilterStore: xFilter
"Evaluate a bicubic (i.e. good quality) interpolation
http://en.wikipedia.org/wiki/Bicubic_interpolation
http://www.engr.mun.ca/~baxter/Publications/ImageZooming.pdf
Like OpenCV's CV_INTER_CUBIC"
| interpolated yIndex0 xIndex0 dy dx uy ux xInSource xInSourceClamped yInSource yInSourceClamped dxSquared dySquared |
xIndex0 _ x floor - 1.
"x < 0.2 ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
xIndex0 < -1 ifTrue: [ ^0.0 ].
(xIndex0 = -1 and: [x < 0.2]) ifTrue: [ ^0.0 ].
"x > (width + 0.8) ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
xIndex0 > width ifTrue: [ ^0.0 ].
(xIndex0 = width and: [x > (width + 0.8)]) ifTrue: [ ^0.0 ].
yIndex0 _ y floor - 1.
"y < 0.2 ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
yIndex0 < -1 ifTrue: [ ^0.0 ].
(yIndex0 = -1 and: [y < 0.2]) ifTrue: [ ^0.0 ].
"y > (height + 0.8) ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
yIndex0 > height ifTrue: [ ^0.0 ].
(yIndex0 = height and: [y > (height + 0.8)]) ifTrue: [ ^0.0 ].
0 to: 3 do: [ :i |
dx _ (x - (xIndex0 + i)) abs.
dxSquared _ dx*dx.
ux _ dx < 2
ifTrue: [
dx < 1
ifTrue: [ (1.5*dx*dxSquared) - (2.5*dxSquared)+1 ]
ifFalse: [ (-0.5*dx*dxSquared) + (2.5*dxSquared) - (4*dx) + 2 ]]
ifFalse: [0.0].
xFilter at: i+1 put: ux ].
interpolated _ 0.0.
0 to: 3 do: [ :i |
dy _ (y - (yIndex0 + i)) abs.
dySquared _ dy*dy.
uy _ dy < 2
ifTrue: [
dy < 1
ifTrue: [ (1.5*dy*dySquared) - (2.5*dySquared)+1 ]
ifFalse: [ (-0.5*dy*dySquared) + (2.5*dySquared) - (4*dy) + 2 ]]
ifFalse: [0.0].
yInSource _ yIndex0 + i.
yInSourceClamped _ yInSource min: height max: 1.
0 to: 3 do: [ :j |
xInSource _ xIndex0 + j.
ux _ xFilter at: j+1.
xInSourceClamped _ xInSource min: width max: 1.
interpolated _ (self x: xInSourceClamped y: yInSourceClamped) * uy * ux + interpolated ]].
^interpolated! !
!FloatImage methodsFor: 'interpolation' stamp: 'jmv 1/21/2016 12:21'!
bilinearInterpolatedClampToEdgeValueAtX: x y: y
"Bilinear interpolation. If acessed outside bounds, clamp to the edge. (Like OpenCL's CLK_ADDRESS_CLAMP_TO_EDGE)"
^ self bilinearInterpolatedValueAtX: (x min: width max: 1) y: (y min: height max: 1)
! !
!FloatImage methodsFor: 'interpolation' stamp: 'jmv 9/3/2020 17:12:45'!
lanczosFilteredValueAtX: x y: y filterScale: filterScale
"Apply a Lanczos resampling filter at x@y, answer the filtered value.
Do not modify receiver in any way.
This method is just a convenience call for #x:y:lanczosFilterScale:xFilterStore:yFilterStore:, useful if a single call is needed.
"
| n xFilter yFilter |
"Build arrays for filters"
n _ (4 / filterScale) floor +1.
xFilter _ Float32Array new: n.
yFilter _ Float32Array new: n.
"And call real method"
^self lanczosFilteredValueAtX: x y: y filterScale: filterScale xFilterStore: xFilter yFilterStore: yFilter! !
!FloatImage methodsFor: 'interpolation' stamp: 'jmv 5/4/2015 13:45'!
lanczosFilteredValueAtX: x y: y filterScale: filterScale xFilterStore: xFilter yFilterStore: yFilter
"Apply a Lanczos resampling filter at x@y, answer the filtered value.
Do not modify receiver in any way.
xFilter and yFilter are filled here, but they are parameters so they can be reused for the many calls to resample an image.
x and y can be any Float values in in [1.0 .. width] and [1.0 .. height].
filterScale is the scale factor of the image resampling for which we build the Lanczos filter.
For example, to build a result image of half the size of the original, use filterScale=0.5.
It must be more than zero, and no more than 1. (0.0 .. 1.0].
"
| xStart xStop yStart yStop xn yn xx yy sum xFilterOffset yFilterOffset xIndex0 yIndex0 |
"Validate inside some reasonable bounds. We allow extending up to 0.8 in any direction"
xIndex0 _ x floor - 1.
"x < 0.2 ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
xIndex0 < -1 ifTrue: [ ^0.0 ].
(xIndex0 = -1 and: [x < 0.2]) ifTrue: [ ^0.0 ].
"x > (width + 0.8) ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
xIndex0 > width ifTrue: [ ^0.0 ].
(xIndex0 = width and: [x > (width + 0.8)]) ifTrue: [ ^0.0 ].
yIndex0 _ y floor - 1.
"y < 0.2 ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
yIndex0 < -1 ifTrue: [ ^0.0 ].
(yIndex0 = -1 and: [y < 0.2]) ifTrue: [ ^0.0 ].
"y > (height + 0.8) ifTrue: [ ^0.0 ]." "Do it with integer arithmetic if at all possible"
yIndex0 > height ifTrue: [ ^0.0 ].
(yIndex0 = height and: [y > (height + 0.8)]) ifTrue: [ ^0.0 ].
"Image area used by the filter. If it goes outside image bounds, it will be corrected below."
xStart _ (x - (2/filterScale)) ceiling.
xStop _ (x + (2/filterScale)) floor.
yStart _ (y - (2/filterScale)) ceiling.
yStop _ (y + (2/filterScale)) floor.
"Filter size"
xn _ xStop - xStart + 1.
yn _ yStop - yStart + 1.