/* Portable 6-dimensional sample arrays. */ /* Last edited on 2005-05-29 10:32:28 by stolfi */ #ifndef ppv_array_H #define ppv_array_H #include #include typedef struct ppv_array_t { ppv_size_t size[6]; /* Extent of array's domain along each axis. */ ppv_step_t step[6]; /* Position increment for each index. */ ppv_pos_t base; /* Position of element {[0,0,0,0,0,0]}. */ ppv_tot_size_t npos; /* Number of distinct sample positions. */ ppv_nbits_t bps; /* Bits per sample. */ ppv_nbits_t bpw; /* Bits per word (storage unit). */ void *el; /* Start of storage area (NULL if no samples). */ } ppv_array_t; /* A {ppv_array_t} is descriptor for a five-dimensional array of small non-negative integers (/samples/), in a packed binary format. Intended use Although a {ppv_array_t} structure can be used to store arbitrary arrays of integers, it was primarily designed to be a unified format for uncompressed, uniformly sampled, muti-channel and multi-dimensional data -- audio, still image (both 2D and 3D) and video. In particular, it was felt that six index dimensions were necessary and sufficient to satisfy most of those applications (e.g. channel + x + y + z + time). Array shape The /shape/ of an array {A} is defined by six non-negative integers, {A.size[0..5]}. Each sample of {A} is identified by six indices {[i[0],..i[5]]}, where index {i[ax]} ranges from 0 to {A.size[ax]-1}, inclusive. Thus, a 1-sec color TV movie could have {size={3,640,480,1,30,1}}, whereas the corresponding 44100 Hz, four-channel, frame-chopped audio file could have {size={4,29400,1,1,30,1}}. Empty arrays If any {size[ax]} is zero, the array has no samples and is said to be /empty/. An empty array still has a definite shape, which is significant in certain operations. Value range of each sample The samples of an array {A} have a definite number of bits per sample, {A.bps}. By definition, each sample is an integer in the range {0..2^A.bps - 1}; in particular, if {bps} is zero, then all samples are zero. Memoryless arrays A /memoryless/ array that is one that is empty or has {bps==0}, and therefore occupies no storage. Sample indexing Each sample of an array {A} also has a /position/ which is an affine combination of its six indices: | pos(A, p,q,r,s,t,u) = A.base + p*A.step[0] + q*A.step[1] + r*A.step[2] + s*A.step[3] + t*A.step[4] + u*A.step[5] Each coefficient {A.step[ax]} may be positive, negative, or zero. In the last case, two samples whose indices differ only in that index will have the same position. The field {A.npos} records the number of distinct sample positions in the array. Sample storage The samples of an array {A} are stored in the area pointed by {A.el}. The position {pos} of a sample, together with the the packing parameters {A.bps} and {A.bpw}, uniquely determines the location of the sample within that area. Two samples with distinct positions occupy disjoint areas of memory. In order to save storage and to simplify image transfer between devices and computers with different architectures, the storage area is organized as a vector of /words/. The number of bits per word is an explicit attribute {A.bpw} of the array, and may be different from the machine's native word size. The legal values for {bpw}, for this implementation, are 8, 16, and 32. Depending on {A.bps} and {A.bpw}, one sample may span two or more words, or one word may contain two or more samples. For further details on how samples are stored, see the comments in {ppv_packing.h}. */ /* INDEX TUPLE MANIPULATION */ void ppv_index_clear ( ppv_index_t *ix ); /* Sets {ix[ax] = 0} for every axis {ax}. */ void ppv_index_assign ( ppv_index_t *ix, ppv_index_t *val ); /* Sets {ix[ax] = val[ax]} for every axis {ax}. */ void ppv_index_shift ( ppv_index_t *ix, ppv_index_t *inc ); /* Sets {ix[ax] += inc[ax]} for every axis {ax}. */ sign_t ppv_index_cmp ( ppv_index_t *ixa, ppv_index_t *ixb ); /* Returns {NEG}, {ZER}, or {POS} depending on whether {ixa} is less than, equal to, or greater than {ixb} in lexicographic order. */ /* INDEX TUPLE VALIDATION */ bool_t ppv_inside ( ppv_array_t *A, ppv_index_t *ix ); /* Returns TRUE iff {ix} is a valid index tuple for the array {A}, i.e., if sample {A[ix[0],..ix[5]]} exists. This is true if and only if {ix[ax]} lies in the range {0..A.step[ax]-1}, for every axis {ax}. */ /* DESCRIPTOR VALIDATION */ bool_t ppv_valid ( ppv_array_t *A ); /* Returns TRUE iff the descriptor {A} seems to be valid. To be valid, an array descriptor must satisfy the following conditions: (1) Two samples with distinct valid index tuples have the same position only if {step[ax]} is zero for every axis where their indices differ. (2) For any {ax}, if {size[ax]} is 1, then {step[ax]} is zero. (3) If the array is memoryless, then {base} and all {step} coefficients are zero, and {el} is NULL. (4) The field {npos} is the number of samples with distinct positions. Because of (3), {npos} is zero if any {size[ax]} is zero, otherwise it is the product of {size[ax]} for every {ax} where {step[ax]} is nonzero. (5) The storage area of every valid sample is contained in the allocated area pointed by {el}. (Note that when {bps==0} this is true even if {el==NULL}.) Condition (1) is hard to check exactly, so this procedure checks instead a more stringent condition: (1') there is a permutation of the indices such that increasing lexicographic order of the permuted index tuples implies increasing sample positions. */ /* ARRAY ALLOCATION */ ppv_array_t ppv_new_array ( ppv_size_t *sz, ppv_nbits_t bps, ppv_nbits_t bpw ); /* Returns a valid descriptor {A} for a newly allocated array of samples, with {A.size = {sz[0],.. sz[5]}} and the specified number of bits per sample and per word. The individual sizes {sz[ax]} must not exceed {ppv_MAX_SIZE}, If the array {A} turns out to be memoryless, {A.el} is set to NULL and all samples will have the same position (0). Otherwise, {A.el} will point to a newly allocated area large enough to contain all samples. The samples {A[ix[0],..ix[5]]} will be packed as compactly as possible, linearized in the obvious way with index {ix[0]} varying fastest and {ix[5]} the slowest. That is, {A.base} will be 0, and each increment {A.step[ax]} will be the product of all sizes {A.size[bx]} with {bx <--1---> <--2---> <--3---> <--4---> | sample# <-0--> <-1--> <-2--> <-3--> <-4--> | ==AAAAAA ==BBBBBB ==CCCCCC ==DDDDDD ==EEEEEE | bit# 7 0 7 0 7 0 7 0 7 0 With {bpw = 16}, we would have {K = 16/6 = 2}, and the following layout: | word# <------0-------> <-----1--------> <-----2--------> | sample# <-0--><-1--> <-2--><-3--> <-4--> | ====AAAAAABBBBBB ====CCCCCCDDDDDD ====EEEEEE====== | bit# 1 1 0 0 1 1 0 0 1 1 0 0 | 5 2 6 0 5 2 6 0 5 2 6 0 With {bpw = 32}, we would have {K = 32/6 = 5}, and the following layout: | word# <---------------------1--------> | sample# <-0--><-1--><-2--><-3--><-4--> | ==AAAAAABBBBBBCCCCCCDDDDDDEEEEEE | bit# 33 2 1 1 0 0 | 10 4 8 2 6 0 Packing of "big" samples If {bps} is greater than {bpw}, then each sample will occupy {K=(bps+bpw-1)/bpw} consecutive words, big-end first. Thus, if {bps = 18}, the layout for {bpw = 8} will be | word# <--0---> <--1---> <--2---> <--3---> <--4---> <--5---> ... | sample# <-------0----------> <--------1---------> ... | ======AA AAAAAAAA AAAAAAAA ======BB BBBBBBBB BBBBBBBB ... | bit# 7 2 0 7 0 7 0 7 2 0 7 0 7 0 ... while that for {bpw = 16} will be | word# <------0-------> <-----1--------> <-----2--------> <-----3--------> ... | sample# <---------0-------> <--------1--------> ... | ==============AA AAAAAAAAAAAAAAAA ==============BB BBBBBBBBBBBBBBBB ... | bit# 1 0 0 1 0 1 0 0 1 0 ... | 5 2 0 5 0 5 2 0 5 0 ... Sample addressing The sample storage area address {A.el} is the address of the word that contains the highest-order bit of the sample with position 0. */ #endif