summaryrefslogtreecommitdiff
path: root/gdk-pixbuf/pixops/DETAILS
blob: 08597f5f785d75366426c5cda442f55715886467 (plain)
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
General ideas of Pixops
=======================

 - Gain speed by special-casing the common case, and using
   generic code to handle the uncommon case.

 - Most of the time in scaling an image is in the center;
   however code that can handle edges properly is slow
   because it needs to deal with the possibility of running
   off the edge. So make the fast case code only handle
   the centers, and use generic, slow, code for the edges,

Structure of Pixops
===================

The code of pixops can roughly be grouped into four parts:

 - Filter computation functions

 - Functions for scaling or compositing lines and pixels
   using precomputed filters

 - pixops process, the central driver that iterates through
   the image calling pixel or line functions as necessary
   
 - Wrapper functions (pixops_scale/composite/composite_color)
   that compute the filter, chooses the line and pixel functions
   and then call pixops_processs with the filter, line,
   and pixel functions.


pixops process is a pretty scary looking function:

static void
pixops_process (guchar         *dest_buf,
		int             render_x0,
		int             render_y0,
		int             render_x1,
		int             render_y1,
		int             dest_rowstride,
		int             dest_channels,
		gboolean        dest_has_alpha,
		const guchar   *src_buf,
		int             src_width,
		int             src_height,
		int             src_rowstride,
		int             src_channels,
		gboolean        src_has_alpha,
		double          scale_x,
		double          scale_y,
		int             check_x,
		int             check_y,
		int             check_size,
		guint32         color1,
		guint32         color2,
		PixopsFilter   *filter,
		PixopsLineFunc  line_func,
		PixopsPixelFunc pixel_func)

(Some of the arguments should be moved into structures. It's basically
"all the arguments to pixops_composite_color plus three more") The
arguments can be divided up into:


Information about the destination buffer

   guchar *dest_buf, int dest_rowstride, int dest_channels, gboolean dest_has_alpha,

Information about the source buffer

   guchar *src_buf,  int src_rowstride,  int src_channels,  gboolean src_has_alpha,
   int src_width, int src_height,

Information on how to scale the source buf and the region of the scaled source
to render onto the destination buffer

   int render_x0, int render_y0, int render_x1, int render_y1
   double scale_x, double scale_y

Information about a constant color or check pattern onto which to to composite

   int check_x,	int check_y, int check_size, guint32 color1, guint32 color2

Information precomputed to use during the scale operation

   PixopsFilter *filter, PixopsLineFunc line_func, OixopsPixelFunc pixel_func


Filter computation
==================

The PixopsFilter structure looks like:

struct _PixopsFilter
{
  int *weights;
  int n_x;
  int n_y;
  double x_offset;
  double y_offset;
}; 


'weights' is an array of size:

 weights[SUBSAMPLE][SUBSAMPLE][n_x][n_y]

SUBSAMPLE is a constant - currently 16 in pixops.c.


In order to compute a scaled destination pixel we convolve
an array of n_x by n_y source pixels with one of
the SUBSAMPLE * SUBSAMPLE filter matrices stored
in weights. The choice of filter matrix is determined
by the fractional part of the source location.

To compute dest[i,j] we do the following:

 x = i * scale_x + x_offset;
 y = i * scale_x + y_offset;
 x_int = floor(x)
 y_int = floor(y)

 C = weights[SUBSAMPLE*(x - x_int)][SUBSAMPLE*(y - y_int)]
 total  = sum[l=0..n_x-1, j=0..n_y-1] (C[l,m] * src[x_int + l, x_int + m])

The filter weights are integers scaled so that the total of the
weights in the weights array is equal to 65536.

When the source does not have alpha, we simply compute each channel
as above, so total is in the range [0,255*65536]

 dest = src / 65536

When the source does have alpha, then we need to compute using
"pre-multiplied alpha":

 a_total = sum (C[l,m] * src_a[x_int + l, x_int + m])
 c_total = sum (C[l,m] * src_a[x_int + l, x_int + m] * src_c[x_int + l, x_int + m])
 
This gives us a result for c_total in the range of [0,255*a_total]
 
 c_dest = c_total / a_total
 

Mathematical aside:

The process of producing a destination filter consists
of:

 - Producing a continuous approximation to the source
   image via interpolation. 

 - Sampling that continuous approximation with filter.

This is representable as:

 S(x,y) = sum[i=-inf,inf; j=-inf,inf] A(frac(x),frac(y))[i,j] * S[floor(x)+i,floor(y)+j]

 D[i,j] = Integral(s=-inf,inf; t=-inf,inf) B(i+x,j+y) S((i+x)/scale_x,(i+y)/scale_y)
 
By reordering the sums and integrals, you get something of the form:

 D[i,j] = sum[l=-inf,inf; m=-inf;inf] C[l,m] S[i+l,j+l]

The arrays in weights are the C[l,m] above, and are thus
determined by the interpolating algorithm in use and the
sampling filter:

                                       INTERPOLATE       SAMPLE
 ART_FILTER_NEAREST                nearest neighbour     point
 ART_FILTER_TILES                  nearest neighbour      box
 ART_FILTER_BILINEAR (scale < 1)   nearest neighbour      box   (scale < 1)
 ART_FILTER_BILINEAR (scale > 1)       bilinear           point  (scale > 1)
 ART_FILTER_HYPER                      bilinear           box
 

Pixel Functions
===============

typedef void (*PixopsPixelFunc) (guchar *dest, int dest_x, int dest_channels, int dest_has_alpha,
				 int src_has_alpha, 
                                 int check_size, guint32 color1, guint32 color2,
				 int r, int g, int b, int a);

The arguments here are:

 dest: location to store the output pixel
 dest_x: x coordinate of destination (for handling checks)
 dest_has_alpha, dest_channels: Information about the destination pixbuf
 src_has_alpha: Information about the source pixbuf

 check_size, color1, color2: Information for color background for composite_color variant
 
 r,g,b,a - scaled red, green, blue and alpha

r,g,b are premultiplied alpha.

 a is in [0,65536*255]
 r is in [0,255*a]
 g is in [0,255*a]
 b is in [0,255*a]

If src_has_alpha is false, then a will be 65536*255, allowing optimization.


Line functions
==============

typedef guchar *(*PixopsLineFunc) (int *weights, int n_x, int n_y,
				   guchar *dest, int dest_x, guchar *dest_end, int dest_channels, int dest_has_alpha,
				   guchar **src, int src_channels, gboolean src_has_alpha,
				   int x_init, int x_step, int src_width,
				   int check_size, guint32 color1, guint32 color2);

The argumets are:

 weights, n_x, n_y

   Filter weights for this row - dimensions weights[SUBSAMPLE][n_x][n_y]

 dest, dest_x, dest_end, dest_channels, dest_has_alpha

   The destination buffer, function will start writing into *dest and
   increment by dest_channels, until dest == dest_end. Reading from
   src for these pixels is guaranteed not to go outside of the 
   bufer bounds

 src, src_channels, src_has_alpha
 
   src[n_y] - an array of pointers to the start of the source rows
   for each filter coordinate.

 x_init, x_step

   Information about x positions in source image.

 src_width - unused

 check_size, color1, color2: Information for color background for composite_color variant

 The total for the destination pixel at dest + i is given by

   SUM (l=0..n_x - 1, m=0..n_y - 1) 
     src[m][(x_init + i * x_step)>> SCALE_SHIFT + l] * weights[m][l]


Algorithms for compositing
==========================

Compositing alpha on non alpha:

 R = As * Rs + (1 - As) * Rd
 G = As * Gs + (1 - As) * Gd
 B = As * Bs + (1 - As) * Bd

This can be regrouped as:

 Cd + Cs * (Cs - Rd)

Compositing alpha on alpha:

 A = As + (1 - As) * Ad
 R = (As * Rs + (1 - As) * Rd * Ad)  / A
 G = (As * Gs + (1 - As) * Gd * Ad)  / A
 B = (As * Bs + (1 - As) * Bd * Ad)  / A

The way to think of this is in terms of the "area":

The final pixel is composed of area As of the source pixel
and (1 - As) * Ad of the target pixel. So the final pixel
is a weighted average with those weights.

Note that the weights do not add up to one - hence the
non-constant division.


Integer tricks for compositing
==============================