@@ -4,6 +4,45 @@ struct Pixel
44 splat_xyz:: Point3f
55end
66Pixel () = Pixel (Point3f (0.0f0 ), 0.0f0 , Point3f (0.0f0 ))
7+
8+
9+ function filter_offset (x, discrete_point, inv_filter_radius, filter_table_width)
10+ fx = abs ((x - discrete_point) * inv_filter_radius * filter_table_width)
11+ return clamp (u_int32 (ceil (fx)), Int32 (1 ), Int32 (filter_table_width)) # TODO is clipping ok?
12+ end
13+
14+
15+ function generate_filter_table (filter)
16+ filter_table_width = 16
17+ filter_table = Matrix {Float32} (undef, filter_table_width, filter_table_width)
18+ r = filter. radius ./ filter_table_width
19+ for y in 0 : filter_table_width- 1 , x in 0 : filter_table_width- 1
20+ p = Point2f ((x + 0.5f0 ) * r[1 ], (y + 0.5f0 ) * r[2 ])
21+ filter_table[y+ 1 , x+ 1 ] = filter (p)
22+ end
23+
24+ point = Point2f (filter_table_width)
25+ # Compute sample's raster bounds.
26+ discrete_point = point .- 0.5f0
27+ # Compute sample radius around point
28+ p0 = ceil .(Int, discrete_point .- filter. radius)
29+ p1 = floor .(Int, discrete_point .+ filter. radius) .+ 1
30+ # Make sure we're inbounds
31+ inv_radius = 1.0f0 ./ filter. radius
32+ # Precompute x & y filter offsets.
33+ offsets_x = filter_offsets (p0[1 ], p1[1 ], discrete_point[1 ], inv_radius[1 ], filter_table_width)
34+ offsets_y = filter_offsets (p0[2 ], p1[2 ], discrete_point[2 ], inv_radius[2 ], filter_table_width)
35+ # Loop over filter support & add sample to pixel array.
36+ xrange = p0[1 ]: p1[1 ]
37+ yrange = p0[2 ]: p1[2 ]
38+ weights = zeros (Float32, length (xrange), length (yrange))
39+ for i in 1 : length (xrange), j in 1 : length (yrange)
40+ w = filter_table[offsets_y[j], offsets_x[i]]
41+ weights[i, j] = w
42+ end
43+ return weights
44+ end
45+
746struct Film{Pixels<: AbstractMatrix{Pixel} }
847 resolution:: Point2f
948 """
@@ -92,153 +131,68 @@ struct FilmTilePixel{S<:Spectrum}
92131end
93132FilmTilePixel () = FilmTilePixel (RGBSpectrum (), 0f0 )
94133
95- struct FilmTile{Pixels<: AbstractMatrix{<:FilmTilePixel} }
96- """
97- Bounds should start from 1 not 0.
98- """
99- bounds:: Bounds2
100- filter_radius:: Point2f
101- inv_filter_radius:: Point2f
102- filter_table_width:: Int32
103- pixels:: Pixels
104- end
105134
106- function FilmTile (
107- bounds:: Bounds2 , filter_radius:: Point2f ,
108- filter_table_width:: Int32 ,
109- )
110- # Include some padding for over rounding (since we re-use the tiles)
111- tile_res = (Int32 .(inclusive_sides (bounds))) .+ 2
112- contrib_sum = fill (RGBSpectrum (), tile_res[2 ], tile_res[1 ])
113- filter_weight_sum = fill (0.0f0 , tile_res[2 ], tile_res[1 ])
114- pixels = StructArray {FilmTilePixel{RGBSpectrum}} ((contrib_sum, filter_weight_sum))
115- FilmTile {typeof(pixels)} (
116- bounds, filter_radius, 1.0f0 ./ filter_radius,
117- filter_table_width,
118- pixels,
119- )
120- end
121135
122136"""
123- Bounds should start from 1 not 0 .
137+ Point in (x, y) format .
124138"""
125- function FilmTile (f:: Film , sample_bounds:: Bounds2 , radius)
126- p0 = ceil .(sample_bounds. p_min .- 0.5f0 .- radius)
127- p1 = floor .(sample_bounds. p_max .- 0.5f0 .+ radius) .+ 1f0
128- tile_bounds = Bounds2 (p0, p1) ∩ f. crop_bounds
129- FilmTile (tile_bounds, radius, f. filter_table_width)
130- end
131-
132- function reset! (tile:: FilmTile )
133-
134- tile. pixels. contrib_sum .= (RGBSpectrum (0f0 ),)
135- tile. pixels. filter_weight_sum .= 0f0
136- end
137-
138- function update_bounds! (f:: Film , tile:: FilmTile , sample_bounds:: Bounds2 )
139- reset! (tile)
140- radius = tile. filter_radius
141- p0 = ceil .(sample_bounds. p_min .- 0.5f0 .- radius)
142- p1 = floor .(sample_bounds. p_max .- 0.5f0 .+ radius) .+ 1.0f0
143- bounds = Bounds2 (p0, p1) ∩ f. crop_bounds
144- tile_res = (Int32 .(inclusive_sides (bounds)))
145- @assert all (reverse (tile_res) .<= size (tile. pixels)) " $(reverse (tile_res)) != $(size (tile. pixels)) $(sample_bounds) "
146- FilmTile (bounds, radius, tile. inv_filter_radius, tile. filter_table_width, tile. pixels)
147- end
148-
149- function filter_offset! (offsets, start, stop, discrete_point, inv_filter_radius, filter_table_width)
150- @inbounds for (i, x) in enumerate (Int (start): Int (stop))
151- fx = abs ((x - discrete_point) * inv_filter_radius * filter_table_width)
152- offsets[i] = clamp (ceil (fx), 1 , filter_table_width) # TODO is clipping ok?
139+ @inline function get_pixel_index (crop_bounds, p:: Point2 )
140+ i1, i2 = u_int32 .((p .- crop_bounds. p_min .+ 1.0f0 ))
141+ return CartesianIndex (i1, i2)
142+ end
143+
144+ @inline function merge_film_tile! (f:: AbstractMatrix{Pixel} , crop_bounds:: Bounds2 , ft:: AbstractMatrix{FilmTilePixel} , tile:: Bounds2 , tile_col:: Int32 )
145+ ft_contrib_sum = ft. contrib_sum
146+ ft_filter_weight_sum = ft. filter_weight_sum
147+ f_xyz = f. xyz
148+ f_filter_weight_sum = f. filter_weight_sum
149+ linear = Int32 (1 )
150+ @inbounds for pixel in tile
151+ f_idx = get_pixel_index (crop_bounds, pixel)
152+ f_xyz[f_idx] += to_XYZ (ft_contrib_sum[linear, tile_col])
153+ f_filter_weight_sum[f_idx] += ft_filter_weight_sum[linear, tile_col]
154+ linear += Int32 (1 )
153155 end
156+ return
154157end
155158
156- @inline function filter_offset (x, discrete_point, inv_filter_radius, filter_table_width)
157- fx = abs ((x - discrete_point) * inv_filter_radius * filter_table_width)
158- return clamp (u_int32 (ceil (fx)), Int32 (1 ), Int32 (filter_table_width)) # TODO is clipping ok?
159- end
160-
161- function filter_offsets (start, stop, discrete_point, inv_filter_radius, filter_table_width):: NTuple{8, Int32}
162- range = Int32 (start): Int32 (stop)
163- return ntuple (8 ) do i
164- if i <= length (range)
165- filter_offset (range[i], discrete_point, inv_filter_radius, filter_table_width):: Int32
166- else
167- Int32 (0 )
168- end
169- end
159+ @inline function get_tile_index (bounds:: Bounds2 , p:: Point2 )
160+ j, i = u_int32 .((p .- bounds. p_min .+ 1.0f0 ))
161+ ncols = u_int32 (inclusive_sides (bounds)[1 ])
162+ return (i - Int32 (1 )) * ncols + j
170163end
171164
172- """
173- Add sample contribution to the film tile.
174-
175- - `point::Point2f`:
176- should start from 1 not 0.
177- And is relative to the film, not the film tile.
178- """
179- function add_sample! (
180- film:: Film , t:: FilmTile , point:: Point2f , spectrum:: S ,
181- sample_weight:: Float32 = 1f0 ,
182- ) where S<: Spectrum
183-
165+ @inline function add_sample! (
166+ tiles:: AbstractMatrix{FilmTilePixel} , tile:: Bounds2 , tile_column:: Int32 , point:: Point2f , spectrum:: RGBSpectrum ,
167+ filter_table, filter_radius:: Point2f , sample_weight:: Float32 = 1.0f0 ,
168+ )
184169 # Compute sample's raster bounds.
185170 discrete_point = point .- 0.5f0
186- p0 = ceil .(Int32, discrete_point .- t. filter_radius)
187- p1 = floor .(Int32, discrete_point .+ t. filter_radius) .+ Int32 (1 )
188- p0 = Int32 .(max .(p0, max .(t. bounds. p_min, Point2 {Int32} (1 ))))
189- p1 = Int32 .(min .(p1, t. bounds. p_max))
190- # Precompute x & y filter offsets.
191- offsets_x = filter_offsets (p0[1 ], p1[1 ], discrete_point[1 ], t. inv_filter_radius[1 ], t. filter_table_width)
192- offsets_y = filter_offsets (p0[2 ], p1[2 ], discrete_point[2 ], t. inv_filter_radius[2 ], t. filter_table_width)
171+ # Compute sample radius around point
172+ p0 = u_int32 .(ceil .(discrete_point .- filter_radius))
173+ p1 = u_int32 .(floor .(discrete_point .+ filter_radius)) .+ Int32 (1 )
174+ # Make sure we're inbounds
175+ pmin = u_int32 .(tile. p_min)
176+ pmax = u_int32 .(tile. p_max)
177+ p0 = max .(p0, max .(pmin, Point2 {Int32} (1 ))):: Point2{Int32}
178+ p1 = min .(p1, pmax):: Point2{Int32}
193179 # Loop over filter support & add sample to pixel array.
194- pixels = t. pixels
195- contrib_sum = pixels. contrib_sum
196- filter_weight_sum = pixels. filter_weight_sum
197- filter_table = film. filter_table
198- @inbounds for (j, y) in enumerate (Int (p0[2 ]): Int (p1[2 ])), (i, x) in enumerate (Int (p0[1 ]): Int (p1[1 ]))
199- w = filter_table[offsets_y[j], offsets_x[i]]
200- @real_assert sample_weight <= 1
201- @real_assert w <= 1
202- idx = get_pixel_index (t, Point2 (x, y))
203- contrib_sum[idx] += spectrum * sample_weight * w
204- filter_weight_sum[idx] += w
180+ contrib_sum = tiles. contrib_sum
181+ filter_weight_sum = tiles. filter_weight_sum
182+ xrange = p0[1 ]: p1[1 ]
183+ yrange = p0[2 ]: p1[2 ]
184+ xn = length (xrange) % Int32
185+ yn = length (yrange) % Int32
186+ @inbounds for i in Int32 (1 ): xn, j in Int32 (1 ): yn
187+ x = xrange[i]
188+ y = yrange[j]
189+ w = filter_table[i, j]
190+ idx = get_tile_index (tile, Point2 (x, y))
191+ contrib_sum[idx, tile_column] += spectrum * sample_weight * w
192+ filter_weight_sum[idx, tile_column] += w
205193 end
206194end
207195
208- """
209- Point in (x, y) format.
210- """
211- @inline function get_pixel_index (t:: FilmTile , p:: Point2 )
212- i1, i2 = u_int32 .((p .- t. bounds. p_min .+ 1f0 ))
213- return CartesianIndex (i2, i1)
214- end
215-
216- """
217- Point in (x, y) format.
218- """
219- @inline function get_pixel_index (f:: Film , p:: Point2 )
220- i1, i2 = u_int32 .((p .- f. crop_bounds. p_min .+ 1f0 ))
221- return CartesianIndex (i2, i1)
222- end
223-
224- function merge_film_tile! (f:: Film , ft:: FilmTile )
225- x_range = Int (ft. bounds. p_min[1 ]): Int (ft. bounds. p_max[1 ])
226- y_range = Int (ft. bounds. p_min[2 ]): Int (ft. bounds. p_max[2 ])
227- ft_contrib_sum = ft. pixels. contrib_sum
228- ft_filter_weight_sum = ft. pixels. filter_weight_sum
229- f_xyz = f. pixels. xyz
230- f_filter_weight_sum = f. pixels. filter_weight_sum
231-
232- @inbounds for y in y_range, x in x_range
233- pixel = Point2 {Int32} (x, y)
234- ft_idx = get_pixel_index (ft, pixel)
235- f_idx = get_pixel_index (f, pixel)
236- f_xyz[f_idx] += to_XYZ (ft_contrib_sum[ft_idx])
237- f_filter_weight_sum[f_idx] += ft_filter_weight_sum[ft_idx]
238- end
239- end
240-
241-
242196function set_image! (f:: Film , spectrum:: Matrix{S} ) where {S<: Spectrum }
243197 @real_assert size (f. pixels) == size (spectrum)
244198 f. pixels. xyz .= to_XYZ .(spectrum)
0 commit comments