Filter Factory - Randomization procedures

Back to technical analysis

There are two procedures which can be called in Filter Factory by the user:

Algorithm

Filter Factory uses Donald E. Knuth's subtractive random number generator algorithm ("ran3"), which has been published at page 283 of "The Art of Computer Programming, volume 2: Seminumerical Algorithms", Addison-Wesley, Reading, MA, second edition, 1981. (PDF Page 307)

Test data

rnd(a,b) will output the following data using default seed rst(0):

00000000  0A 23 6A 73 9E 6F 78 5B 86 58 25 91 40 75 7D A4
00000010  2B 31 CD BE 5C A7 E3 7E 30 0C 4F B5 11 D2 24 20
00000020  B1 01 4D 83 4B AA CE 9E 82 96 E9 36 5C A7 5F 01
00000030  B6 A3 1C 32 AB 66 10 EA 72 69 26 1B 24 CE 8D E8
00000040  D6 8F A8 0A 19 D6 45 2A 7B 2A A2 2A FC 7D 6E 46
...
00010000  66 E7 61 34 8F 5C 63 5D 35 2A C9 56 E2 D7 B2 21
00010010  32 06 34 7B 30 A9 79 0E 37 29 09 E8 12 C5 FA B6
00010020  B5 BA 28 C9 66 BC 1C 84 56 21 1F 2D 9A 39 68 3C
00010030  BB 5F 04 62 96 DA 4D B0 32 A7 0C C6 F6 A7 41 B1
00010040  D4 A8 37 B5 3D 79 B9 F6 4B D5 77 CE 13 9F C1 87
...

Whole code in C++

uint16_t RND_INDEX_COUNTER[2]; // [PTR DS:[1C006004]]+0094h
uint32_t RND_LOOKUP[56];       // [PTR DS:[1C006004]]+0098h
uint32_t RND_SEED = 0;         // [PTR DS:[1C006004]]+4578h
uint32_t RND_SEED_SAVE = 0;    // [PTR DS:[1C006004]]+457Ch

uint32_t rnd(uint32_t a, uint32_t b) {
	RND_INDEX_COUNTER[0] = RND_INDEX_COUNTER[0] % 55 + 1;
	RND_INDEX_COUNTER[1] = RND_INDEX_COUNTER[1] % 55 + 1;

	uint32_t result = RND_LOOKUP[RND_INDEX_COUNTER[0]] - RND_LOOKUP[RND_INDEX_COUNTER[1]];
	RND_LOOKUP[RND_INDEX_COUNTER[0]] = result;

	// Scale result to interval [a..b]
	int32_t range = b - a;
	if (range < 0) return 0; // this check was not existing in 3.00a
	return a + (result % (range + 1));
}

void fill_rnd_lookup(uint32_t param_seed) {
	unsigned int iVar2;
	unsigned int iVar3;
	unsigned int iVar5;
	unsigned int iVar6;

	// 161803398 = 1.61803398 * 10^8 ~ phi * 10^8
	iVar5 = 161803398 - (param_seed & 0x7fff);
	RND_LOOKUP[55] = iVar5;

	iVar6 = 1;
	for (iVar2 = 21; iVar2 <= 1134; iVar2 += 21) {
		iVar3 = iVar6;
		RND_LOOKUP[iVar2 % 55] = iVar3;
		iVar6 = iVar5 - iVar3;
		iVar5 = iVar3;
	}

	for (iVar2 =0; iVar2 <4; iVar2++) {
		unsigned int cnt = 1;
		do {
			RND_LOOKUP[cnt++] = RND_LOOKUP[cnt] - RND_LOOKUP[((cnt + 30) % 55) + 1];
		} while (cnt <= 55);
	}

	RND_SEED_SAVE = param_seed;

	return;
}

uint32_t rst(uint32_t seed) {
	RND_SEED = seed;
	return 0;
}

void initialize_variables() {
	RND_INDEX_COUNTER[0] = 0;
	RND_INDEX_COUNTER[1] = 31;
	fill_rnd_lookup(0);
}

int main()
{
	initialize_variables();

	// Optional: Set individual seed
	rst(1);
	if (RND_SEED != RND_SEED_SAVE) {
		fill_rnd_lookup(RND_SEED);
	}

	// Print 10 random numbers
	for (int i=0; i<10; i++) {
		printf("%d\n", rnd(0, 255));
	}
}

Disassembly of the functions handling randomization

Global variables

In C++:

uint16_t RND_INDEX_COUNTER[2]; // [PTR DS:[1C006004]]+0094h
uint32_t RND_LOOKUP[56];       // [PTR DS:[1C006004]]+0098h
uint32_t RND_SEED = 0;         // [PTR DS:[1C006004]]+4578h
uint32_t RND_SEED_SAVE = 0;    // [PTR DS:[1C006004]]+457Ch

Procedure rnd(a,b)

Procedure is stored in the OPER resource.

Assembly (Filter Factory 3.0.4 for Photoshop/Windows):

8D 9F 94 00 00 00 lea ebx,[edi+$00000094 (RND_INDEX_COUNTER)]
66 8B 43 02 mov ax,[ebx+$02 (2)] ax = RND_INDEX_COUNTER[1];
66 40 inc ax ax++;
66 3D 38 00 cmp ax,$0038 (56) .
75 04 jnz +$04 (@@1) if (ax != 56) goto @@1;
66 B8 01 00 mov ax,$0001 (1) ax = 1;
@@1: @@1:
66 89 43 02 mov [ebx+$02 (2)],ax RND_INDEX_COUNTER[1] = ax;
66 8B 03 mov ax,[ebx] ax = RND_INDEX_COUNTER[0];
66 40 inc ax ax++;
66 3D 38 00 cmp ax,$0038 (56) .
75 04 jnz +$04 (@@2) if (ax != 56) goto @@2;
66 B8 01 00 mov ax,$0001 (1) ax = 1;
@@2: @@2:
66 89 03 mov [ebx],ax RND_INDEX_COUNTER[0] = ax;
0F B7 03 movzx eax,[ebx] eax = RND_INDEX_COUNTER[0];
8D 4C 83 04 lea ecx,[ebx+eax*4+$04 (4)] T = eax;
8B 01 mov eax,[ecx] eax = RND_LOOKUP[T];
0F B6 53 02 movzx edx,[ebx+$02 (2)] edx = RND_INDEX_COUNTER[1];
2B 44 93 04 sub eax,[ebx+edx*4+$04 (4)] eax -= RND_LOOKUP[edx]; // eax = RND_LOOKUP[RND_INDEX_COUNTER[0]] - RND_LOOKUP[RND_INDEX_COUNTER[1]]
89 01 mov [ecx],eax RND_LOOKUP[T] = eax;
5E pop esi (param_b) .
59 pop ecx (param_a) .
2B F1 sub esi,ecx esi = param_b - param_a;
79 04 jns +$04 (@@3) if (esi >= 0) goto @@3;
33 C0 xor eax eax = 0 (Error: param_a > param_b . Return 0)
EB 08 jmp +$08 (@@4) goto @@4;
@@3: @@3:
46 inc esi esi++;
2B D2 sub edx,edx edx = 0;
F7 F6 div esi eax /= esi; edx = eax / esi
8D 04 11 lea eax,[ecx+edx] eax = ecx + edx;
@@4: @@4:
50 push eax return eax; // return param_a + (RND_LOOKUP[T] % (param_b - param_a + 1));

In C++:

uint32_t rnd(uint32_t a, uint32_t b) {
	RND_INDEX_COUNTER[0] = RND_INDEX_COUNTER[0] % 55 + 1;
	RND_INDEX_COUNTER[1] = RND_INDEX_COUNTER[1] % 55 + 1;

	uint32_t result = RND_LOOKUP[RND_INDEX_COUNTER[0]] - RND_LOOKUP[RND_INDEX_COUNTER[1]];
	RND_LOOKUP[RND_INDEX_COUNTER[0]] = result;

	// Scale result to interval [a..b]
	int32_t range = b - a;
	if (range < 0) return 0; // this check was not existing in 3.00a
	return a + (result % (range + 1));
}

Procedure rst(i)

Procedure is stored in the OPER resource.

Writes parameter to EDI[$4578] (RND_SEED) and returns 0.

Assembly (Filter Factory 3.0.4 for Photoshop/Windows):

58 pop eax (param_i) .
89 87 78 45 00 00 mov [edi+$00004578 (RND_SEED)],eax RND_SEED = param_i;
2B C0 sub eax,eax param_i = 0;
50 push eax return param_i;

In C++:

uint32_t rst(uint32_t seed) {
	RND_SEED = seed;
	return 0;
}

Pixel processing

Before a pixel is processed (?), the RND_LOOKUP table will be initialized, if the user has chosen a new individual seed.

Assembly (Filter Factory 3.0.4 for Photoshop/Windows):

.....
.text:1C003661 loc_1C003661:
.text:1C003661                 mov     eax, ds:dword_1C006004
.text:1C003666                 mov     ecx, [eax+4578h] ; RND_SEED
.text:1C00366C                 mov     edx, [eax+457Ch] ; RND_SEED_SAVE
.text:1C003672                 cmp     edx, ecx
.text:1C003674                 jz      short loc_1C00369D
.text:1C003676                 mov     edx, eax
.text:1C003678                 mov     ebx, [eax+44h]  ; xmin
.text:1C00367B                 cmp     [edx+40h], ebx  ; x
.text:1C00367E                 jnz     short loc_1C00369D
.text:1C003680                 mov     edx, eax
.text:1C003682                 mov     ebx, [eax+4Ch]  ; y
.text:1C003685                 cmp     [edx+50h], ebx  ; ymin
.text:1C003688                 jnz     short loc_1C00369D
.text:1C00368A                 mov     edx, eax
.text:1C00368C                 mov     ebx, [eax+5Ch]  ; zmin
.text:1C00368F                 cmp     [edx+58h], ebx  ; z
.text:1C003692                 jnz     short loc_1C00369D
.text:1C003694                 push    ecx
.text:1C003695                 call    fill_rnd_lookup ; fill if x==y==z==0 & RND_SEED!=RND_SEED_SAVE
.text:1C00369A                 add     esp, 4
.text:1C00369D
.text:1C00369D loc_1C00369D:
.text:1C00369D                 .....

In C++:

unsigned int process_pixel() {
	// ...
	if ((x==xmin) && (y==ymin) && (z==zmin) && (RND_SEED != RND_SEED_SAVE)) {
		fill_rnd_lookup(RND_SEED);
	}
	// ...
}

Procedure fill_rnd_lookup(arg_seed):

fill_rnd_lookup() will be called at two locations:

Assembly (Filter Factory 3.0.4 for Photoshop/Windows):

 1C0039B0: 53                 push        ebx
1C0039B1: 56                 push        esi
1C0039B2: 57                 push        edi
1C0039B3: 55                 push        ebp
1C0039B4: 8B 35 04 60 00 1C  mov         esi,dword ptr ds:[1C006004h]
1C0039BA: BF 86 EC A4 09     mov         edi,9A4EC86h                      edi = 161803398 (= 1.61803398 * 10^8 ≈ φ * 10^8)
1C0039BF: 81 C6 98 00 00 00  add         esi,98h                           esi = RND_LOOKUP
1C0039C5: 8B 44 24 14        mov         eax,dword ptr [esp+14h]           .
1C0039C9: 25 FF 7F 00 00     and         eax,7FFFh                         eax = arg_seed & 0x7FFF
1C0039CE: BB 01 00 00 00     mov         ebx,1                             ebx = 1
1C0039D3: 2B F8              sub         edi,eax                           edi = 161803398 - (arg_seed & 0x7FFF)
1C0039D5: B9 15 00 00 00     mov         ecx,15h                           ecx = 21
1C0039DA: 89 BE DC 00 00 00  mov         dword ptr [esi+000000DCh],edi     RND_LOOKUP[55] = edi
@@1:
1C0039E0: BD 37 00 00 00     mov         ebp,37h                           ebp = 55
1C0039E5: 8B C1              mov         eax,ecx                           eax = ecx
1C0039E7: 99                 cdq
1C0039E8: F7 FD              idiv        ebp                               eax = eax / ebp
                                                                           edx = eax % ebp
1C0039EA: 8B C3              mov         eax,ebx                           eax = ebx
1C0039EC: 2B FB              sub         edi,ebx                           edi -= ebx
1C0039EE: 89 04 96           mov         dword ptr [esi+edx*4],eax         RND_LOOKUP[edx] = eax
1C0039F1: 8B DF              mov         ebx,edi                           ebx = edi
1C0039F3: 8B F8              mov         edi,eax                           edi = eax
1C0039F5: 83 C1 15           add         ecx,15h                           ecx += 21
1C0039F8: 81 F9 6E 04 00 00  cmp         ecx,46Eh
1C0039FE: 7E E0              jle         1C0039E0                          if (ecx <= 1134) jmp @@1
1C003A00: BF 04 00 00 00     mov         edi,4                             edi = 4
@@2:
1C003A05: BB 01 00 00 00     mov         ebx,1                             ebx = 1
1C003A0A: 8D 9B 00 00 00 00  lea         ebx,[ebx+00000000h]               (nop)
@@3:
1C003A10: 8D 43 1E           lea         eax,[ebx+1Eh]                     eax = ebx + 30
1C003A13: B9 37 00 00 00     mov         ecx,37h                           ecx = 55
1C003A18: 99                 cdq
1C003A19: F7 F9              idiv        ecx                               eax = eax / ecx
                                                                           edx = eax % ecx
1C003A1B: 8B 0C 9E           mov         ecx,dword ptr [esi+ebx*4]         ecx = RND_LOOKUP[ebx]
1C003A1E: 43                 inc         ebx                               ebx++
1C003A1F: 8B 44 96 04        mov         eax,dword ptr [esi+edx*4+4]       eax = RND_LOOKUP[edx+1]
1C003A23: 2B C8              sub         ecx,eax                           ecx -= eax
1C003A25: 83 FB 37           cmp         ebx,37h                           ebx == 55 ?
1C003A28: 89 4C 9E FC        mov         dword ptr [esi+ebx*4-4],ecx       RND_LOOKUP[ebx-1] = ecx
1C003A2C: 7E E2              jle         1C003A10                          if (ebx<=55) jmp @@3
1C003A2E: 4F                 dec         edi                               edi--
1C003A2F: 90                 nop
1C003A30: 75 D3              jne         1C003A05                          if (ebx!=55) jmp @@2
1C003A32: 8B 0D 04 60 00 1C  mov         ecx,dword ptr ds:[1C006004h]
1C003A38: 8B 44 24 14        mov         eax,dword ptr [esp+14h]           eax = arg_seed
1C003A3C: 89 81 7C 45 00 00  mov         dword ptr [ecx+0000457Ch],eax     RND_SEED_SAVE = eax
1C003A42: 5D                 pop         ebp
1C003A43: 5F                 pop         edi
1C003A44: 5E                 pop         esi
1C003A45: 5B                 pop         ebx
1C003A46: C3                 ret

In C++:

void fill_rnd_lookup(uint32_t param_seed) {
	unsigned int iVar2;
	unsigned int iVar3;
	unsigned int iVar5;
	unsigned int iVar6;

	// 161803398 = 1.61803398 * 10^8 ~ phi * 10^8
	iVar5 = 161803398 - (param_seed & 0x7fff);
	RND_LOOKUP[55] = iVar5;

	iVar6 = 1;
	for (iVar2 = 21; iVar2 <= 1134; iVar2 += 21) {
		iVar3 = iVar6;
		RND_LOOKUP[iVar2 % 55] = iVar3;
		iVar6 = iVar5 - iVar3;
		iVar5 = iVar3;
	}

	for (iVar2 =0; iVar2 <4; iVar2++) {
		unsigned int cnt = 1;
		do {
			RND_LOOKUP[cnt++] = RND_LOOKUP[cnt] - RND_LOOKUP[((cnt + 30) % 55) + 1];
		} while (cnt <= 55);
	}

	RND_SEED_SAVE = param_seed;

	return;
}

Procedure initialize_variables()

This procedure initializes (among other things) the counters RND_INDEX_COUNTER[0] and RND_INDEX_COUNTER[1].

Assembly (Filter Factory 3.0.4 for Photoshop/Windows):

 1C003350: 53                 push        ebx
1C003351: 2B D2              sub         edx,edx
@@1:
1C003353: A1 00 60 00 1C     mov         eax,[1C006000]
1C003358: 8B 0D 04 60 00 1C  mov         ecx,dword ptr ds:[1C006004h]
1C00335E: 8B 5C 10 08        mov         ebx,dword ptr [eax+edx+8]
1C003362: 83 C2 04           add         edx,4
1C003365: 89 5C 11 60        mov         dword ptr [ecx+edx+60h],ebx
1C003369: 83 FA 20           cmp         edx,20h
1C00336C: 7C E5              jl          1C003353(@@1)
1C00336E: A1 04 60 00 1C     mov         eax,[1C006004]
1C003373: 05 78 01 00 00     add         eax,178h                           map[0]
1C003378: 50                 push        eax
1C003379: A1 04 60 00 1C     mov         eax,[1C006004]
1C00337E: 8B 48 64           mov         ecx,dword ptr [eax+64h]            ctl[0]
1C003381: 8B 50 68           mov         edx,dword ptr [eax+68h]            ctl[1]
1C003384: 51                 push        ecx
1C003385: 52                 push        edx
1C003386: E8 C5 04 00 00     call        1C003850
1C00338B: 83 C4 0C           add         esp,0Ch
1C00338E: A1 04 60 00 1C     mov         eax,[1C006004]
1C003393: 05 78 02 00 00     add         eax,278h                           map[1]
1C003398: 50                 push        eax
1C003399: A1 04 60 00 1C     mov         eax,[1C006004]
1C00339E: 8B 48 6C           mov         ecx,dword ptr [eax+6Ch]            ctl[2]
1C0033A1: 8B 50 70           mov         edx,dword ptr [eax+70h]            ctl[3]
1C0033A4: 51                 push        ecx
1C0033A5: 52                 push        edx
1C0033A6: E8 A5 04 00 00     call        1C003850
1C0033AB: 83 C4 0C           add         esp,0Ch
1C0033AE: A1 04 60 00 1C     mov         eax,[1C006004]
1C0033B3: 05 78 03 00 00     add         eax,378h                           map[2]
1C0033B8: 50                 push        eax
1C0033B9: A1 04 60 00 1C     mov         eax,[1C006004]
1C0033BE: 8B 48 74           mov         ecx,dword ptr [eax+74h]            ctl[4]
1C0033C1: 8B 50 78           mov         edx,dword ptr [eax+78h]            ctl[5]
1C0033C4: 51                 push        ecx
1C0033C5: 52                 push        edx
1C0033C6: E8 85 04 00 00     call        1C003850
1C0033CB: 83 C4 0C           add         esp,0Ch
1C0033CE: A1 04 60 00 1C     mov         eax,[1C006004]
1C0033D3: 05 78 04 00 00     add         eax,478h                           map[3]
1C0033D8: 50                 push        eax
1C0033D9: A1 04 60 00 1C     mov         eax,[1C006004]
1C0033DE: 8B 48 7C           mov         ecx,dword ptr [eax+7Ch]            ctl[6]
1C0033E1: 8B 90 80 00 00 00  mov         edx,dword ptr [eax+80h]            ctl[7]
1C0033E7: 51                 push        ecx
1C0033E8: 52                 push        edx
1C0033E9: E8 62 04 00 00     call        1C003850
1C0033EE: 83 C4 0C           add         esp,0Ch
1C0033F1: 8B 0D 04 60 00 1C  mov         ecx,dword ptr ds:[1C006004h]
1C0033F7: 66 C7 81 94 00 00  mov         word ptr [ecx+00000094h],0         RND_INDEX_COUNTER[0] = 0
          00 00 00
1C003400: 8B 0D 04 60 00 1C  mov         ecx,dword ptr ds:[1C006004h]
1C003406: 6A 00              push        0                                  param_seed = 0;
1C003408: 66 C7 81 96 00 00  mov         word ptr [ecx+00000096h],1Fh       RND_INDEX_COUNTER[1] = 31
          00 1F 00
1C003411: E8 9A 05 00 00     call        1C0039B0                           call fill_rnd_lookup(param_seed)
1C003416: 83 C4 04           add         esp,4
1C003419: A1 04 60 00 1C     mov         eax,[1C006004]
1C00341E: 05 78 35 00 00     add         eax,3578h                          cell[0..255]
1C003423: 50                 push        eax
1C003424: E8 37 07 00 00     call        1C003B60
1C003429: 83 C4 04           add         esp,4
1C00342C: 5B                 pop         ebx
1C00342D: C3                 ret

In C++:

void initialize_variables() {
	// ...
	RND_INDEX_COUNTER[0] = 0;
	RND_INDEX_COUNTER[1] = 31;
	fill_rnd_lookup(0);
	// ...
}

Random analysis using the tool "ent"

Microsoft Visual C++ rand()&0xFF using srand(0), 512 MiB data

seed = seed * 214013 + 2531011

Entropy = 8.000000 bits per byte.

Optimum compression would reduce the size
of this 536870912 byte file by 0 percent.

Chi square distribution for 536870912 samples is 0.00, and randomly
would exceed this value more than than 99.99 percent of the times.

Arithmetic mean value of data bytes is 127.5000 (127.5 = random).
Monte Carlo value for Pi is 3.141621005 (error 0.00 percent).
Serial correlation coefficient is 0.000011 (totally uncorrelated = 0.0).

OpenWatcom C++ rand()&0xFF using srand(0), 512 MiB data

seed = seed * 1103515245 + 12345

Entropy = 8.000000 bits per byte.

Optimum compression would reduce the size
of this 536870912 byte file by 0 percent.

Chi square distribution for 536870912 samples is 0.00, and randomly
would exceed this value more than than 99.99 percent of the times.

Arithmetic mean value of data bytes is 127.5000 (127.5 = random).
Monte Carlo value for Pi is 3.141647872 (error 0.00 percent).
Serial correlation coefficient is -0.000001 (totally uncorrelated = 0.0).

Filter Factory algorithm rnd(0,255) using rst(0), 512 MiB data

Entropy = 8.000000 bits per byte.

Optimum compression would reduce the size
of this 536870912 byte file by 0 percent.

Chi square distribution for 536870912 samples is 245.05, and randomly
would exceed this value 66.16 percent of the times.

Arithmetic mean value of data bytes is 127.4980 (127.5 = random).
Monte Carlo value for Pi is 3.141413402 (error 0.01 percent).
Serial correlation coefficient is 0.000125 (totally uncorrelated = 0.0).