1 | // Most code come from original Allegro rotation code: |
2 | // By Shawn Hargreaves. |
3 | // Flipping routines by Andrew Geers. |
4 | // Optimized by Sven Sandberg. |
5 | // To C++ templates by David Capello |
6 | // |
7 | // This file is released under the terms of the MIT license. |
8 | // Read LICENSE.txt for more information. |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "base/pi.h" |
15 | #include "doc/blend_funcs.h" |
16 | #include "doc/image_impl.h" |
17 | #include "doc/mask.h" |
18 | #include "doc/primitives.h" |
19 | #include "doc/primitives_fast.h" |
20 | #include "fixmath/fixmath.h" |
21 | |
22 | #include <cmath> |
23 | |
24 | namespace doc { |
25 | namespace algorithm { |
26 | |
27 | using namespace fixmath; |
28 | |
29 | static void ase_parallelogram_map_standard( |
30 | Image* bmp, const Image* sprite, const Image* mask, |
31 | fixed xs[4], fixed ys[4]); |
32 | |
33 | static void ase_rotate_scale_flip_coordinates( |
34 | fixed w, fixed h, |
35 | fixed x, fixed y, |
36 | fixed cx, fixed cy, |
37 | fixed angle, |
38 | fixed scale_x, fixed scale_y, |
39 | int h_flip, int v_flip, |
40 | fixed xs[4], fixed ys[4]); |
41 | |
42 | template<typename ImageTraits, typename BlendFunc> |
43 | static void image_scale_tpl( |
44 | Image* dst, const Image* src, |
45 | int dst_x, int dst_y, int dst_w, int dst_h, |
46 | int src_x, int src_y, int src_w, int src_h, BlendFunc blend) |
47 | { |
48 | LockImageBits<ImageTraits> dst_bits(dst, gfx::Rect(dst_x, dst_y, dst_w, dst_h)); |
49 | typename LockImageBits<ImageTraits>::iterator dst_it = dst_bits.begin(); |
50 | fixed x, first_x = itofix(src_x); |
51 | fixed y = itofix(src_y); |
52 | fixed dx = fixdiv(itofix(src_w-1), itofix(dst_w-1)); |
53 | fixed dy = fixdiv(itofix(src_h-1), itofix(dst_h-1)); |
54 | int old_x, new_x; |
55 | |
56 | for (int v=0; v<dst_h; ++v) { |
57 | old_x = fixtoi(x = first_x); |
58 | |
59 | const LockImageBits<ImageTraits> src_bits(src, gfx::Rect(src_x, fixtoi(y), src_w, 1)); |
60 | auto src_it = src_bits.begin(); |
61 | |
62 | for (int u=0; u<dst_w; ++u) { |
63 | ASSERT(dst_it != dst_bits.end()); |
64 | |
65 | *dst_it = blend(*dst_it, *src_it); |
66 | ++dst_it; |
67 | |
68 | x = fixadd(x, dx); |
69 | new_x = fixtoi(x); |
70 | if (old_x != new_x) { |
71 | // We don't want to move the "src_it" iterator outside the src |
72 | // image bounds. |
73 | if (new_x < src_w) { |
74 | src_it += (new_x - old_x); |
75 | old_x = new_x; |
76 | } |
77 | else |
78 | break; |
79 | } |
80 | } |
81 | |
82 | y = fixadd(y, dy); |
83 | } |
84 | } |
85 | |
86 | static color_t rgba_blender(color_t back, color_t front) { |
87 | return rgba_blender_normal(back, front); |
88 | } |
89 | |
90 | static color_t grayscale_blender(color_t back, color_t front) { |
91 | return graya_blender_normal(back, front); |
92 | } |
93 | |
94 | class if_blender { |
95 | public: |
96 | if_blender(color_t mask) : m_mask(mask) { |
97 | } |
98 | color_t operator()(color_t back, color_t front) { |
99 | if (front != m_mask) |
100 | return front; |
101 | else |
102 | return back; |
103 | } |
104 | private: |
105 | color_t m_mask; |
106 | }; |
107 | |
108 | void scale_image(Image* dst, const Image* src, |
109 | int dst_x, int dst_y, int dst_w, int dst_h, |
110 | int src_x, int src_y, int src_w, int src_h) |
111 | { |
112 | gfx::Clip clip(dst_x, dst_y, src_x, src_y, dst_w, dst_h); |
113 | if (src_w == dst_w && src_h == dst_h) { |
114 | dst->copy(src, clip); |
115 | return; |
116 | } |
117 | |
118 | if (!clip.clip(dst->width(), dst->height(), src->width(), src->height())) |
119 | return; |
120 | |
121 | switch (dst->pixelFormat()) { |
122 | |
123 | case IMAGE_RGB: |
124 | image_scale_tpl<RgbTraits>( |
125 | dst, src, |
126 | dst_x, dst_y, dst_w, dst_h, |
127 | src_x, src_y, src_w, src_h, rgba_blender); |
128 | break; |
129 | |
130 | case IMAGE_GRAYSCALE: |
131 | image_scale_tpl<GrayscaleTraits>( |
132 | dst, src, |
133 | dst_x, dst_y, dst_w, dst_h, |
134 | src_x, src_y, src_w, src_h, grayscale_blender); |
135 | break; |
136 | |
137 | case IMAGE_INDEXED: |
138 | image_scale_tpl<IndexedTraits>( |
139 | dst, src, |
140 | dst_x, dst_y, dst_w, dst_h, |
141 | src_x, src_y, src_w, src_h, if_blender(src->maskColor())); |
142 | break; |
143 | |
144 | case IMAGE_BITMAP: |
145 | image_scale_tpl<BitmapTraits>( |
146 | dst, src, |
147 | dst_x, dst_y, dst_w, dst_h, |
148 | src_x, src_y, src_w, src_h, if_blender(0)); |
149 | break; |
150 | } |
151 | } |
152 | |
153 | void rotate_image(Image* dst, const Image* src, int x, int y, int w, int h, |
154 | int cx, int cy, double angle) |
155 | { |
156 | fixed xs[4], ys[4]; |
157 | |
158 | ase_rotate_scale_flip_coordinates(itofix(src->width()), itofix (src->height()), |
159 | itofix(x), itofix(y), |
160 | itofix(cx), itofix(cy), |
161 | ftofix(256 * angle / PI), |
162 | fixdiv(itofix(w), itofix(src->width())), |
163 | fixdiv(itofix(h), itofix(src->height())), |
164 | false, false, xs, ys); |
165 | |
166 | ase_parallelogram_map_standard(dst, src, nullptr, xs, ys); |
167 | } |
168 | |
169 | /* 1-----2 |
170 | | | |
171 | 4-----3 |
172 | */ |
173 | void parallelogram(Image* bmp, const Image* sprite, const Image* mask, |
174 | int x1, int y1, int x2, int y2, |
175 | int x3, int y3, int x4, int y4) |
176 | { |
177 | fixed xs[4], ys[4]; |
178 | |
179 | xs[0] = itofix(x1); |
180 | ys[0] = itofix(y1); |
181 | xs[1] = itofix(x2); |
182 | ys[1] = itofix(y2); |
183 | xs[2] = itofix(x3); |
184 | ys[2] = itofix(y3); |
185 | xs[3] = itofix(x4); |
186 | ys[3] = itofix(y4); |
187 | |
188 | ase_parallelogram_map_standard(bmp, sprite, mask, xs, ys); |
189 | } |
190 | |
191 | // Scanline drawers. |
192 | |
193 | template<class Traits, class Delegate> |
194 | static void draw_scanline( |
195 | Image* bmp, |
196 | const Image* spr, |
197 | const Image* mask, |
198 | fixed l_bmp_x, int bmp_y_i, |
199 | fixed r_bmp_x, |
200 | fixed l_spr_x, fixed l_spr_y, |
201 | fixed spr_dx, fixed spr_dy, |
202 | Delegate& delegate) |
203 | { |
204 | r_bmp_x >>= 16; |
205 | l_bmp_x >>= 16; |
206 | |
207 | delegate.lockBits(bmp, gfx::Rect(l_bmp_x, bmp_y_i, r_bmp_x - l_bmp_x + 1, 1)); |
208 | |
209 | gfx::Rect maskBounds = (mask ? mask->bounds(): spr->bounds()); |
210 | |
211 | for (int x=(int)l_bmp_x; x<=(int)r_bmp_x; ++x) { |
212 | int u = l_spr_x>>16; |
213 | int v = l_spr_y>>16; |
214 | |
215 | if (!mask || |
216 | (maskBounds.contains(u, v) && get_pixel_fast<BitmapTraits>(mask, u, v))) |
217 | delegate.putPixel(spr, u, v); |
218 | delegate.nextPixel(); |
219 | |
220 | l_spr_x += spr_dx; |
221 | l_spr_y += spr_dy; |
222 | } |
223 | |
224 | delegate.unlockBits(); |
225 | } |
226 | |
227 | template<class Traits> |
228 | class GenericDelegate { |
229 | public: |
230 | void lockBits(Image* bmp, const gfx::Rect& bounds) { |
231 | m_bits = bmp->lockBits<Traits>(Image::ReadWriteLock, bounds); |
232 | m_it = m_bits.begin(); |
233 | m_end = m_bits.end(); |
234 | } |
235 | |
236 | void unlockBits() { |
237 | m_bits.unlock(); |
238 | } |
239 | |
240 | void nextPixel() { |
241 | ASSERT(m_it != m_end); |
242 | ++m_it; |
243 | } |
244 | |
245 | private: |
246 | ImageBits<Traits> m_bits; |
247 | |
248 | protected: |
249 | typename LockImageBits<Traits>::iterator m_it, m_end; |
250 | }; |
251 | |
252 | class RgbDelegate : public GenericDelegate<RgbTraits> { |
253 | public: |
254 | RgbDelegate(color_t mask_color) { |
255 | m_mask_color = mask_color; |
256 | } |
257 | |
258 | void putPixel(const Image* spr, int spr_x, int spr_y) { |
259 | ASSERT(m_it != m_end); |
260 | |
261 | int c = get_pixel_fast<RgbTraits>(spr, spr_x, spr_y); |
262 | if ((rgba_geta(m_mask_color) == 0) || ((c & rgba_rgb_mask) != (m_mask_color & rgba_rgb_mask))) |
263 | *m_it = rgba_blender_normal(*m_it, c); |
264 | } |
265 | |
266 | private: |
267 | color_t m_mask_color; |
268 | }; |
269 | |
270 | class GrayscaleDelegate : public GenericDelegate<GrayscaleTraits> { |
271 | public: |
272 | GrayscaleDelegate(color_t mask_color) { |
273 | m_mask_color = mask_color; |
274 | } |
275 | |
276 | void putPixel(const Image* spr, int spr_x, int spr_y) { |
277 | ASSERT(m_it != m_end); |
278 | |
279 | int c = get_pixel_fast<GrayscaleTraits>(spr, spr_x, spr_y); |
280 | if ((graya_geta(m_mask_color) == 0) || ((c & graya_v_mask) != (m_mask_color & graya_v_mask))) |
281 | *m_it = graya_blender_normal(*m_it, c, 255); |
282 | } |
283 | |
284 | private: |
285 | color_t m_mask_color; |
286 | }; |
287 | |
288 | class IndexedDelegate : public GenericDelegate<IndexedTraits> { |
289 | public: |
290 | IndexedDelegate(color_t mask_color) : |
291 | m_mask_color(mask_color) { |
292 | } |
293 | |
294 | void putPixel(const Image* spr, int spr_x, int spr_y) { |
295 | ASSERT(m_it != m_end); |
296 | |
297 | color_t c = get_pixel_fast<IndexedTraits>(spr, spr_x, spr_y); |
298 | if (c != m_mask_color) |
299 | *m_it = c; |
300 | } |
301 | |
302 | private: |
303 | color_t m_mask_color; |
304 | }; |
305 | |
306 | class BitmapDelegate : public GenericDelegate<BitmapTraits> { |
307 | public: |
308 | void putPixel(const Image* spr, int spr_x, int spr_y) { |
309 | ASSERT(m_it != m_end); |
310 | |
311 | int c = get_pixel_fast<BitmapTraits>(spr, spr_x, spr_y); |
312 | if (c != 0) // TODO |
313 | *m_it = c; |
314 | } |
315 | }; |
316 | |
317 | /* _parallelogram_map: |
318 | * Worker routine for drawing rotated and/or scaled and/or flipped sprites: |
319 | * It actually maps the sprite to any parallelogram-shaped area of the |
320 | * bitmap. The top left corner is mapped to (xs[0], ys[0]), the top right to |
321 | * (xs[1], ys[1]), the bottom right to x (xs[2], ys[2]), and the bottom left |
322 | * to (xs[3], ys[3]). The corners are assumed to form a perfect |
323 | * parallelogram, i.e. xs[0]+xs[2] = xs[1]+xs[3]. The corners are given in |
324 | * fixed point format, so xs[] and ys[] are coordinates of the outer corners |
325 | * of corner pixels in clockwise order beginning with top left. |
326 | * All coordinates begin with 0 in top left corner of pixel (0, 0). So a |
327 | * rotation by 0 degrees of a sprite to the top left of a bitmap can be |
328 | * specified with coordinates (0, 0) for the top left pixel in source |
329 | * bitmap. With the default scanline drawer, a pixel in the destination |
330 | * bitmap is drawn if and only if its center is covered by any pixel in the |
331 | * sprite. The color of this covering sprite pixel is used to draw. |
332 | * If sub_pixel_accuracy=false, then the scanline drawer will be called with |
333 | * *_bmp_x being a fixed point representation of the integers representing |
334 | * the x coordinate of the first and last point in bmp whose centre is |
335 | * covered by the sprite. If sub_pixel_accuracy=true, then the scanline |
336 | * drawer will be called with the exact fixed point position of the first |
337 | * and last point in which the horizontal line passing through the centre is |
338 | * at least partly covered by the sprite. This is useful for doing |
339 | * anti-aliased blending. |
340 | */ |
341 | template<class Traits, class Delegate> |
342 | static void ase_parallelogram_map( |
343 | Image* bmp, const Image* spr, const Image* mask, |
344 | fixed xs[4], fixed ys[4], |
345 | int sub_pixel_accuracy, Delegate delegate) |
346 | { |
347 | /* Index in xs[] and ys[] to topmost point. */ |
348 | int top_index; |
349 | /* Rightmost point has index (top_index+right_index) int xs[] and ys[]. */ |
350 | int right_index; |
351 | /* Loop variables. */ |
352 | int index, i; |
353 | /* Coordinates in bmp ordered as top-right-bottom-left. */ |
354 | fixed corner_bmp_x[4], corner_bmp_y[4]; |
355 | /* Coordinates in spr ordered as top-right-bottom-left. */ |
356 | fixed corner_spr_x[4], corner_spr_y[4]; |
357 | /* y coordinate of bottom point, left point and right point. */ |
358 | int clip_bottom_i, l_bmp_y_bottom_i, r_bmp_y_bottom_i; |
359 | /* Left and right clipping. */ |
360 | fixed clip_left, clip_right; |
361 | /* Temporary variable. */ |
362 | fixed ; |
363 | |
364 | /* |
365 | * Variables used in the loop |
366 | */ |
367 | /* Coordinates of sprite and bmp points in beginning of scanline. */ |
368 | fixed l_spr_x, l_spr_y, l_bmp_x, l_bmp_dx; |
369 | /* Increment of left sprite point as we move a scanline down. */ |
370 | fixed l_spr_dx, l_spr_dy; |
371 | /* Coordinates of sprite and bmp points in end of scanline. */ |
372 | fixed r_bmp_x, r_bmp_dx; |
373 | #ifdef KEEP_TRACK_OF_RIGHT_SPRITE_SCANLINE |
374 | fixed r_spr_x, r_spr_y; |
375 | /* Increment of right sprite point as we move a scanline down. */ |
376 | fixed r_spr_dx, r_spr_dy; |
377 | #endif |
378 | /* Increment of sprite point as we move right inside a scanline. */ |
379 | fixed spr_dx, spr_dy; |
380 | /* Positions of beginning of scanline after rounding to integer coordinate |
381 | in bmp. */ |
382 | fixed l_spr_x_rounded, l_spr_y_rounded, l_bmp_x_rounded; |
383 | fixed r_bmp_x_rounded; |
384 | /* Current scanline. */ |
385 | int bmp_y_i; |
386 | /* Right edge of scanline. */ |
387 | int right_edge_test; |
388 | |
389 | /* Get index of topmost point. */ |
390 | top_index = 0; |
391 | if (ys[1] < ys[0]) |
392 | top_index = 1; |
393 | if (ys[2] < ys[top_index]) |
394 | top_index = 2; |
395 | if (ys[3] < ys[top_index]) |
396 | top_index = 3; |
397 | |
398 | /* Get direction of points: clockwise or anti-clockwise. */ |
399 | if (fixmul(xs[(top_index+1) & 3] - xs[top_index], |
400 | ys[(top_index-1) & 3] - ys[top_index]) > |
401 | fixmul(xs[(top_index-1) & 3] - xs[top_index], |
402 | ys[(top_index+1) & 3] - ys[top_index])) |
403 | right_index = 1; |
404 | else |
405 | right_index = -1; |
406 | |
407 | /* |
408 | * Get coordinates of the corners. |
409 | */ |
410 | |
411 | /* corner_*[0] is top, [1] is right, [2] is bottom, [3] is left. */ |
412 | index = top_index; |
413 | for (i = 0; i < 4; i++) { |
414 | corner_bmp_x[i] = xs[index]; |
415 | corner_bmp_y[i] = ys[index]; |
416 | if (index < 2) |
417 | corner_spr_y[i] = 0; |
418 | else |
419 | /* Need `- 1' since otherwise it would be outside sprite. */ |
420 | corner_spr_y[i] = (spr->height() << 16) - 1; |
421 | if ((index == 0) || (index == 3)) |
422 | corner_spr_x[i] = 0; |
423 | else |
424 | corner_spr_x[i] = (spr->width() << 16) - 1; |
425 | index = (index + right_index) & 3; |
426 | } |
427 | |
428 | /* |
429 | * Get scanline starts, ends and deltas, and clipping coordinates. |
430 | */ |
431 | #define top_bmp_y corner_bmp_y[0] |
432 | #define right_bmp_y corner_bmp_y[1] |
433 | #define bottom_bmp_y corner_bmp_y[2] |
434 | #define left_bmp_y corner_bmp_y[3] |
435 | #define top_bmp_x corner_bmp_x[0] |
436 | #define right_bmp_x corner_bmp_x[1] |
437 | #define bottom_bmp_x corner_bmp_x[2] |
438 | #define left_bmp_x corner_bmp_x[3] |
439 | #define top_spr_y corner_spr_y[0] |
440 | #define right_spr_y corner_spr_y[1] |
441 | #define bottom_spr_y corner_spr_y[2] |
442 | #define left_spr_y corner_spr_y[3] |
443 | #define top_spr_x corner_spr_x[0] |
444 | #define right_spr_x corner_spr_x[1] |
445 | #define bottom_spr_x corner_spr_x[2] |
446 | #define left_spr_x corner_spr_x[3] |
447 | |
448 | /* Calculate left and right clipping. */ |
449 | clip_left = 0; |
450 | clip_right = (bmp->width() << 16) - 1; |
451 | |
452 | /* Quit if we're totally outside. */ |
453 | if ((left_bmp_x > clip_right) && |
454 | (top_bmp_x > clip_right) && |
455 | (bottom_bmp_x > clip_right)) |
456 | return; |
457 | if ((right_bmp_x < clip_left) && |
458 | (top_bmp_x < clip_left) && |
459 | (bottom_bmp_x < clip_left)) |
460 | return; |
461 | |
462 | /* Bottom clipping. */ |
463 | if (sub_pixel_accuracy) |
464 | clip_bottom_i = (bottom_bmp_y + 0xffff) >> 16; |
465 | else |
466 | clip_bottom_i = (bottom_bmp_y + 0x8000) >> 16; |
467 | |
468 | if (clip_bottom_i > bmp->height()) |
469 | clip_bottom_i = bmp->height(); |
470 | |
471 | /* Calculate y coordinate of first scanline. */ |
472 | if (sub_pixel_accuracy) |
473 | bmp_y_i = top_bmp_y >> 16; |
474 | else |
475 | bmp_y_i = (top_bmp_y + 0x8000) >> 16; |
476 | |
477 | if (bmp_y_i < 0) |
478 | bmp_y_i = 0; |
479 | |
480 | /* Sprite is above or below bottom clipping area. */ |
481 | if (bmp_y_i >= clip_bottom_i) |
482 | return; |
483 | |
484 | /* Vertical gap between top corner and centre of topmost scanline. */ |
485 | extra_scanline_fraction = (bmp_y_i << 16) + 0x8000 - top_bmp_y; |
486 | /* Calculate x coordinate of beginning of scanline in bmp. */ |
487 | l_bmp_dx = fixdiv(left_bmp_x - top_bmp_x, |
488 | left_bmp_y - top_bmp_y); |
489 | l_bmp_x = top_bmp_x + fixmul(extra_scanline_fraction, l_bmp_dx); |
490 | /* Calculate x coordinate of beginning of scanline in spr. */ |
491 | /* note: all these are rounded down which is probably a Good Thing (tm) */ |
492 | l_spr_dx = fixdiv(left_spr_x - top_spr_x, |
493 | left_bmp_y - top_bmp_y); |
494 | l_spr_x = top_spr_x + fixmul(extra_scanline_fraction, l_spr_dx); |
495 | /* Calculate y coordinate of beginning of scanline in spr. */ |
496 | l_spr_dy = fixdiv(left_spr_y - top_spr_y, |
497 | left_bmp_y - top_bmp_y); |
498 | l_spr_y = top_spr_y + fixmul(extra_scanline_fraction, l_spr_dy); |
499 | |
500 | /* Calculate left loop bound. */ |
501 | l_bmp_y_bottom_i = (left_bmp_y + 0x8000) >> 16; |
502 | if (l_bmp_y_bottom_i > clip_bottom_i) |
503 | l_bmp_y_bottom_i = clip_bottom_i; |
504 | |
505 | /* Calculate x coordinate of end of scanline in bmp. */ |
506 | r_bmp_dx = fixdiv(right_bmp_x - top_bmp_x, |
507 | right_bmp_y - top_bmp_y); |
508 | r_bmp_x = top_bmp_x + fixmul(extra_scanline_fraction, r_bmp_dx); |
509 | #ifdef KEEP_TRACK_OF_RIGHT_SPRITE_SCANLINE |
510 | /* Calculate x coordinate of end of scanline in spr. */ |
511 | r_spr_dx = fixdiv(right_spr_x - top_spr_x, |
512 | right_bmp_y - top_bmp_y); |
513 | r_spr_x = top_spr_x + fixmul(extra_scanline_fraction, r_spr_dx); |
514 | /* Calculate y coordinate of end of scanline in spr. */ |
515 | r_spr_dy = fixdiv(right_spr_y - top_spr_y, |
516 | right_bmp_y - top_bmp_y); |
517 | r_spr_y = top_spr_y + fixmul(extra_scanline_fraction, r_spr_dy); |
518 | #endif |
519 | |
520 | /* Calculate right loop bound. */ |
521 | r_bmp_y_bottom_i = (right_bmp_y + 0x8000) >> 16; |
522 | |
523 | /* Get dx and dy, the offsets to add to the source coordinates as we move |
524 | one pixel rightwards along a scanline. This formula can be derived by |
525 | considering the 2x2 matrix that transforms the sprite to the |
526 | parallelogram. |
527 | We'd better use double to get this as exact as possible, since any |
528 | errors will be accumulated along the scanline. |
529 | */ |
530 | spr_dx = (fixed)((ys[3] - ys[0]) * 65536.0 * (65536.0 * spr->width()) / |
531 | ((xs[1] - xs[0]) * (double)(ys[3] - ys[0]) - |
532 | (xs[3] - xs[0]) * (double)(ys[1] - ys[0]))); |
533 | spr_dy = (fixed)((ys[1] - ys[0]) * 65536.0 * (65536.0 * spr->height()) / |
534 | ((xs[3] - xs[0]) * (double)(ys[1] - ys[0]) - |
535 | (xs[1] - xs[0]) * (double)(ys[3] - ys[0]))); |
536 | |
537 | /* |
538 | * Loop through scanlines. |
539 | */ |
540 | |
541 | while (1) { |
542 | /* Has beginning of scanline passed a corner? */ |
543 | if (bmp_y_i >= l_bmp_y_bottom_i) { |
544 | /* Are we done? */ |
545 | if (bmp_y_i >= clip_bottom_i) |
546 | break; |
547 | |
548 | /* Vertical gap between left corner and centre of scanline. */ |
549 | extra_scanline_fraction = (bmp_y_i << 16) + 0x8000 - left_bmp_y; |
550 | /* Update x coordinate of beginning of scanline in bmp. */ |
551 | l_bmp_dx = fixdiv(bottom_bmp_x - left_bmp_x, |
552 | bottom_bmp_y - left_bmp_y); |
553 | l_bmp_x = left_bmp_x + fixmul(extra_scanline_fraction, l_bmp_dx); |
554 | /* Update x coordinate of beginning of scanline in spr. */ |
555 | l_spr_dx = fixdiv(bottom_spr_x - left_spr_x, |
556 | bottom_bmp_y - left_bmp_y); |
557 | l_spr_x = left_spr_x + fixmul(extra_scanline_fraction, l_spr_dx); |
558 | /* Update y coordinate of beginning of scanline in spr. */ |
559 | l_spr_dy = fixdiv(bottom_spr_y - left_spr_y, |
560 | bottom_bmp_y - left_bmp_y); |
561 | l_spr_y = left_spr_y + fixmul(extra_scanline_fraction, l_spr_dy); |
562 | |
563 | /* Update loop bound. */ |
564 | if (sub_pixel_accuracy) |
565 | l_bmp_y_bottom_i = (bottom_bmp_y + 0xffff) >> 16; |
566 | else |
567 | l_bmp_y_bottom_i = (bottom_bmp_y + 0x8000) >> 16; |
568 | if (l_bmp_y_bottom_i > clip_bottom_i) |
569 | l_bmp_y_bottom_i = clip_bottom_i; |
570 | } |
571 | |
572 | /* Has end of scanline passed a corner? */ |
573 | if (bmp_y_i >= r_bmp_y_bottom_i) { |
574 | /* Vertical gap between right corner and centre of scanline. */ |
575 | extra_scanline_fraction = (bmp_y_i << 16) + 0x8000 - right_bmp_y; |
576 | /* Update x coordinate of end of scanline in bmp. */ |
577 | r_bmp_dx = fixdiv(bottom_bmp_x - right_bmp_x, |
578 | bottom_bmp_y - right_bmp_y); |
579 | r_bmp_x = right_bmp_x + fixmul(extra_scanline_fraction, r_bmp_dx); |
580 | #ifdef KEEP_TRACK_OF_RIGHT_SPRITE_SCANLINE |
581 | /* Update x coordinate of beginning of scanline in spr. */ |
582 | r_spr_dx = fixdiv(bottom_spr_x - right_spr_x, |
583 | bottom_bmp_y - right_bmp_y); |
584 | r_spr_x = right_spr_x + fixmul(extra_scanline_fraction, r_spr_dx); |
585 | /* Update y coordinate of beginning of scanline in spr. */ |
586 | r_spr_dy = fixdiv(bottom_spr_y - right_spr_y, |
587 | bottom_bmp_y - right_bmp_y); |
588 | r_spr_y = right_spr_y + fixmul(extra_scanline_fraction, r_spr_dy); |
589 | #endif |
590 | |
591 | /* Update loop bound: We aren't supposed to use this any more, so |
592 | just set it to some big enough value. */ |
593 | r_bmp_y_bottom_i = clip_bottom_i; |
594 | } |
595 | |
596 | /* Make left bmp coordinate be an integer and clip it. */ |
597 | if (sub_pixel_accuracy) |
598 | l_bmp_x_rounded = l_bmp_x; |
599 | else |
600 | l_bmp_x_rounded = (l_bmp_x + 0x8000) & ~0xffff; |
601 | if (l_bmp_x_rounded < clip_left) |
602 | l_bmp_x_rounded = clip_left; |
603 | |
604 | /* ... and move starting point in sprite accordingly. */ |
605 | if (sub_pixel_accuracy) { |
606 | l_spr_x_rounded = l_spr_x + |
607 | fixmul((l_bmp_x_rounded - l_bmp_x), spr_dx); |
608 | l_spr_y_rounded = l_spr_y + |
609 | fixmul((l_bmp_x_rounded - l_bmp_x), spr_dy); |
610 | } |
611 | else { |
612 | l_spr_x_rounded = l_spr_x + |
613 | fixmul(l_bmp_x_rounded + 0x7fff - l_bmp_x, spr_dx); |
614 | l_spr_y_rounded = l_spr_y + |
615 | fixmul(l_bmp_x_rounded + 0x7fff - l_bmp_x, spr_dy); |
616 | } |
617 | |
618 | /* Make right bmp coordinate be an integer and clip it. */ |
619 | if (sub_pixel_accuracy) |
620 | r_bmp_x_rounded = r_bmp_x; |
621 | else |
622 | r_bmp_x_rounded = (r_bmp_x - 0x8000) & ~0xffff; |
623 | if (r_bmp_x_rounded > clip_right) |
624 | r_bmp_x_rounded = clip_right; |
625 | |
626 | /* Draw! */ |
627 | if (l_bmp_x_rounded <= r_bmp_x_rounded) { |
628 | if (!sub_pixel_accuracy) { |
629 | /* The bodies of these ifs are only reached extremely seldom, |
630 | it's an ugly hack to avoid reading outside the sprite when |
631 | the rounding errors are accumulated the wrong way. It would |
632 | be nicer if we could ensure that this never happens by making |
633 | all multiplications and divisions be rounded up or down at |
634 | the correct places. |
635 | I did try another approach: recalculate the edges of the |
636 | scanline from scratch each scanline rather than incrementally. |
637 | Drawing a sprite with that routine took about 25% longer time |
638 | though. |
639 | */ |
640 | if ((unsigned)(l_spr_x_rounded >> 16) >= (unsigned)spr->width()) { |
641 | if (((l_spr_x_rounded < 0) && (spr_dx <= 0)) || |
642 | ((l_spr_x_rounded > 0) && (spr_dx >= 0))) { |
643 | /* This can happen. */ |
644 | goto skip_draw; |
645 | } |
646 | else { |
647 | /* I don't think this can happen, but I can't prove it. */ |
648 | do { |
649 | l_spr_x_rounded += spr_dx; |
650 | l_bmp_x_rounded += 65536; |
651 | if (l_bmp_x_rounded > r_bmp_x_rounded) |
652 | goto skip_draw; |
653 | } while ((unsigned)(l_spr_x_rounded >> 16) >= |
654 | (unsigned)spr->width()); |
655 | |
656 | } |
657 | } |
658 | right_edge_test = l_spr_x_rounded + |
659 | ((r_bmp_x_rounded - l_bmp_x_rounded) >> 16) * |
660 | spr_dx; |
661 | if ((unsigned)(right_edge_test >> 16) >= (unsigned)spr->width()) { |
662 | if (((right_edge_test < 0) && (spr_dx <= 0)) || |
663 | ((right_edge_test > 0) && (spr_dx >= 0))) { |
664 | /* This can happen. */ |
665 | do { |
666 | r_bmp_x_rounded -= 65536; |
667 | right_edge_test -= spr_dx; |
668 | if (l_bmp_x_rounded > r_bmp_x_rounded) |
669 | goto skip_draw; |
670 | } while ((unsigned)(right_edge_test >> 16) >= |
671 | (unsigned)spr->width()); |
672 | } |
673 | else { |
674 | /* I don't think this can happen, but I can't prove it. */ |
675 | goto skip_draw; |
676 | } |
677 | } |
678 | if ((unsigned)(l_spr_y_rounded >> 16) >= (unsigned)spr->height()) { |
679 | if (((l_spr_y_rounded < 0) && (spr_dy <= 0)) || |
680 | ((l_spr_y_rounded > 0) && (spr_dy >= 0))) { |
681 | /* This can happen. */ |
682 | goto skip_draw; |
683 | } |
684 | else { |
685 | /* I don't think this can happen, but I can't prove it. */ |
686 | do { |
687 | l_spr_y_rounded += spr_dy; |
688 | l_bmp_x_rounded += 65536; |
689 | if (l_bmp_x_rounded > r_bmp_x_rounded) |
690 | goto skip_draw; |
691 | } while (((unsigned)l_spr_y_rounded >> 16) >= |
692 | (unsigned)spr->height()); |
693 | } |
694 | } |
695 | right_edge_test = l_spr_y_rounded + |
696 | ((r_bmp_x_rounded - l_bmp_x_rounded) >> 16) * |
697 | spr_dy; |
698 | if ((unsigned)(right_edge_test >> 16) >= (unsigned)spr->height()) { |
699 | if (((right_edge_test < 0) && (spr_dy <= 0)) || |
700 | ((right_edge_test > 0) && (spr_dy >= 0))) { |
701 | /* This can happen. */ |
702 | do { |
703 | r_bmp_x_rounded -= 65536; |
704 | right_edge_test -= spr_dy; |
705 | if (l_bmp_x_rounded > r_bmp_x_rounded) |
706 | goto skip_draw; |
707 | } while ((unsigned)(right_edge_test >> 16) >= |
708 | (unsigned)spr->height()); |
709 | } |
710 | else { |
711 | /* I don't think this can happen, but I can't prove it. */ |
712 | goto skip_draw; |
713 | } |
714 | } |
715 | } |
716 | draw_scanline<Traits, Delegate>(bmp, spr, mask, |
717 | l_bmp_x_rounded, bmp_y_i, r_bmp_x_rounded, |
718 | l_spr_x_rounded, l_spr_y_rounded, |
719 | spr_dx, spr_dy, delegate); |
720 | |
721 | } |
722 | /* I'm not going to apoligize for this label and its gotos: to get |
723 | rid of it would just make the code look worse. */ |
724 | skip_draw: |
725 | |
726 | /* Jump to next scanline. */ |
727 | bmp_y_i++; |
728 | /* Update beginning of scanline. */ |
729 | l_bmp_x += l_bmp_dx; |
730 | l_spr_x += l_spr_dx; |
731 | l_spr_y += l_spr_dy; |
732 | /* Update end of scanline. */ |
733 | r_bmp_x += r_bmp_dx; |
734 | #ifdef KEEP_TRACK_OF_RIGHT_SPRITE_SCANLINE |
735 | r_spr_x += r_spr_dx; |
736 | r_spr_y += r_spr_dy; |
737 | #endif |
738 | } |
739 | } |
740 | |
741 | /* _parallelogram_map_standard: |
742 | * Helper function for calling _parallelogram_map() with the appropriate |
743 | * scanline drawer. I didn't want to include this in the |
744 | * _parallelogram_map() function since then you can bypass it and define |
745 | * your own scanline drawer, eg. for anti-aliased rotations. |
746 | */ |
747 | static void ase_parallelogram_map_standard( |
748 | Image* bmp, const Image* sprite, const Image* mask, |
749 | fixed xs[4], fixed ys[4]) |
750 | { |
751 | switch (bmp->pixelFormat()) { |
752 | |
753 | case IMAGE_RGB: { |
754 | RgbDelegate delegate(sprite->maskColor()); |
755 | ase_parallelogram_map<RgbTraits, RgbDelegate>(bmp, sprite, mask, xs, ys, false, delegate); |
756 | break; |
757 | } |
758 | |
759 | case IMAGE_GRAYSCALE: { |
760 | GrayscaleDelegate delegate(sprite->maskColor()); |
761 | ase_parallelogram_map<GrayscaleTraits, GrayscaleDelegate>(bmp, sprite, mask, xs, ys, false, delegate); |
762 | break; |
763 | } |
764 | |
765 | case IMAGE_INDEXED: { |
766 | IndexedDelegate delegate(sprite->maskColor()); |
767 | ase_parallelogram_map<IndexedTraits, IndexedDelegate>(bmp, sprite, mask, xs, ys, false, delegate); |
768 | break; |
769 | } |
770 | |
771 | case IMAGE_BITMAP: { |
772 | BitmapDelegate delegate; |
773 | ase_parallelogram_map<BitmapTraits, BitmapDelegate>(bmp, sprite, mask, xs, ys, false, delegate); |
774 | break; |
775 | } |
776 | } |
777 | } |
778 | |
779 | /* _rotate_scale_flip_coordinates: |
780 | * Calculates the coordinates for the rotated, scaled and flipped sprite, |
781 | * and passes them on to the given function. |
782 | */ |
783 | static void ase_rotate_scale_flip_coordinates(fixed w, fixed h, |
784 | fixed x, fixed y, |
785 | fixed cx, fixed cy, |
786 | fixed angle, |
787 | fixed scale_x, fixed scale_y, |
788 | int h_flip, int v_flip, |
789 | fixed xs[4], fixed ys[4]) |
790 | { |
791 | fixed fix_cos, fix_sin; |
792 | int tl = 0, tr = 1, bl = 3, br = 2; |
793 | int tmp; |
794 | double cos_angle, sin_angle; |
795 | fixed xofs, yofs; |
796 | |
797 | /* Setting angle to the range -180...180 degrees makes sin & cos |
798 | more numerically stable. (Yes, this does have an effect for big |
799 | angles!) Note that using "real" sin() and cos() gives much better |
800 | precision than fixsin() and fixcos(). */ |
801 | angle = angle & 0xffffff; |
802 | if (angle >= 0x800000) |
803 | angle -= 0x1000000; |
804 | |
805 | cos_angle = cos(angle * (PI / (double)0x800000)); |
806 | sin_angle = sin(angle * (PI / (double)0x800000)); |
807 | |
808 | if (cos_angle >= 0) |
809 | fix_cos = (int)(cos_angle * 0x10000 + 0.5); |
810 | else |
811 | fix_cos = (int)(cos_angle * 0x10000 - 0.5); |
812 | if (sin_angle >= 0) |
813 | fix_sin = (int)(sin_angle * 0x10000 + 0.5); |
814 | else |
815 | fix_sin = (int)(sin_angle * 0x10000 - 0.5); |
816 | |
817 | /* Decide what order to take corners in. */ |
818 | if (v_flip) { |
819 | tl = 3; |
820 | tr = 2; |
821 | bl = 0; |
822 | br = 1; |
823 | } |
824 | else { |
825 | tl = 0; |
826 | tr = 1; |
827 | bl = 3; |
828 | br = 2; |
829 | } |
830 | if (h_flip) { |
831 | tmp = tl; |
832 | tl = tr; |
833 | tr = tmp; |
834 | tmp = bl; |
835 | bl = br; |
836 | br = tmp; |
837 | } |
838 | |
839 | /* Calculate new coordinates of all corners. */ |
840 | w = fixmul(w, scale_x); |
841 | h = fixmul(h, scale_y); |
842 | cx = fixmul(cx, scale_x); |
843 | cy = fixmul(cy, scale_y); |
844 | |
845 | xofs = x - fixmul(cx, fix_cos) + fixmul(cy, fix_sin); |
846 | |
847 | yofs = y - fixmul(cx, fix_sin) - fixmul(cy, fix_cos); |
848 | |
849 | xs[tl] = xofs; |
850 | ys[tl] = yofs; |
851 | xs[tr] = xofs + fixmul(w, fix_cos); |
852 | ys[tr] = yofs + fixmul(w, fix_sin); |
853 | xs[bl] = xofs - fixmul(h, fix_sin); |
854 | ys[bl] = yofs + fixmul(h, fix_cos); |
855 | |
856 | xs[br] = xs[tr] + xs[bl] - xs[tl]; |
857 | ys[br] = ys[tr] + ys[bl] - ys[tl]; |
858 | } |
859 | |
860 | } // namespace algorithm |
861 | } // namespace doc |
862 | |