Browse Source

Implement bilinear interpolation for rectangular sprites

rexim 2 years ago
parent
commit
c1d006d4fb
4 changed files with 125 additions and 7 deletions
  1. 24 2
      nobuild.c
  2. 88 4
      olive.c
  3. 13 1
      test.c
  4. BIN
      test/bilinear_interpolation_expected.png

+ 24 - 2
nobuild.c

@@ -177,8 +177,30 @@ int main(int argc, char **argv)
             // TODO: pass arguments to the ./build/test command
             build_tests();
         } else if (strcmp(subcmd, "demos") == 0) {
-            // TODO: build specific demos for the specific platforms
-            build_all_vc_demos();
+            if (argc > 0) {
+                const char *name = shift_args(&argc, &argv);
+
+                if (argc > 0) {
+                    const char *platform = shift_args(&argc, &argv);
+                    if (strcmp(platform, "sdl") == 0) {
+                        pid_wait(build_sdl_demo(name));
+                    } else if (strcmp(platform, "term") == 0) {
+                        pid_wait(build_term_demo(name));
+                    } else if (strcmp(platform, "wasm") == 0) {
+                        pid_wait(build_wasm_demo(name));
+                        copy_file(CONCAT("./build/demos/", name, ".wasm"), CONCAT("./wasm/", name, ".wasm"));
+                    } else {
+                        PANIC("unknown demo platform %s", platform);
+                    }
+                } else {
+                    Pids pids = {0};
+                    build_vc_demo(name, &pids);
+                    pids_wait(pids);
+                    copy_file(CONCAT("./build/demos/", name, ".wasm"), CONCAT("./wasm/", name, ".wasm"));
+                }
+            } else {
+                build_all_vc_demos();
+            }
         } else {
             PANIC("Unknown command `%s`", subcmd);
         }

+ 88 - 4
olive.c

@@ -342,6 +342,7 @@ OLIVECDEF void olivec_triangle3uv(Olivec_Canvas oc, int x1, int y1, int x2, int
 OLIVECDEF void olivec_text(Olivec_Canvas oc, const char *text, int x, int y, Olivec_Font font, size_t size, uint32_t color);
 OLIVECDEF void olivec_sprite_blend(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite);
 OLIVECDEF void olivec_sprite_copy(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite);
+OLIVECDEF void olivec_sprite_copy_bilinear(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite);
 
 typedef struct {
     // Safe ranges to iterate over.
@@ -581,6 +582,32 @@ OLIVECDEF void olivec_line(Olivec_Canvas oc, int x1, int y1, int x2, int y2, uin
     }
 }
 
+OLIVECDEF uint32_t mix_colors2(uint32_t c1, uint32_t c2, int u1, int det)
+{
+    // TODO: estimate how much overflows are an issue in integer only environment
+    int64_t r1 = OLIVEC_RED(c1);
+    int64_t g1 = OLIVEC_GREEN(c1);
+    int64_t b1 = OLIVEC_BLUE(c1);
+    int64_t a1 = OLIVEC_ALPHA(c1);
+
+    int64_t r2 = OLIVEC_RED(c2);
+    int64_t g2 = OLIVEC_GREEN(c2);
+    int64_t b2 = OLIVEC_BLUE(c2);
+    int64_t a2 = OLIVEC_ALPHA(c2);
+
+    if (det != 0) {
+        int u2 = det - u1;
+        int64_t r4 = (r1*u2 + r2*u1)/det;
+        int64_t g4 = (g1*u2 + g2*u1)/det;
+        int64_t b4 = (b1*u2 + b2*u1)/det;
+        int64_t a4 = (a1*u2 + a2*u1)/det;
+
+        return OLIVEC_RGBA(r4, g4, b4, a4);
+    }
+
+    return 0;
+}
+
 OLIVECDEF uint32_t mix_colors3(uint32_t c1, uint32_t c2, uint32_t c3, int u1, int u2, int det)
 {
     // TODO: estimate how much overflows are an issue in integer only environment
@@ -620,10 +647,10 @@ OLIVECDEF bool olivec_barycentric(int x1, int y1, int x2, int y2, int x3, int y3
     *u2  = ((y3 - y1)*(xp - x3) + (x1 - x3)*(yp - y3));
     int u3 = *det - *u1 - *u2;
     return (
-        (OLIVEC_SIGN(int, *u1) == OLIVEC_SIGN(int, *det) || *u1 == 0) &&
-        (OLIVEC_SIGN(int, *u2) == OLIVEC_SIGN(int, *det) || *u2 == 0) &&
-        (OLIVEC_SIGN(int, u3) == OLIVEC_SIGN(int, *det) || u3 == 0)
-    );
+               (OLIVEC_SIGN(int, *u1) == OLIVEC_SIGN(int, *det) || *u1 == 0) &&
+               (OLIVEC_SIGN(int, *u2) == OLIVEC_SIGN(int, *det) || *u2 == 0) &&
+               (OLIVEC_SIGN(int, u3) == OLIVEC_SIGN(int, *det) || u3 == 0)
+           );
 }
 
 OLIVECDEF bool olivec_normalize_triangle(size_t width, size_t height, int x1, int y1, int x2, int y2, int x3, int y3, int *lx, int *hx, int *ly, int *hy)
@@ -773,6 +800,8 @@ OLIVECDEF void olivec_sprite_copy(Olivec_Canvas oc, int x, int y, int w, int h,
     if (sprite.width == 0) return;
     if (sprite.height == 0) return;
 
+    // TODO: consider introducing flip parameter instead of relying on negative width and height
+    // Similar to how SDL_RenderCopyEx does that
     Olivec_Normalized_Rect nr = {0};
     if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return;
 
@@ -789,6 +818,61 @@ OLIVECDEF void olivec_sprite_copy(Olivec_Canvas oc, int x, int y, int w, int h,
     }
 }
 
+OLIVECDEF void olivec_sprite_copy_bilinear(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite)
+{
+    // TODO: support negative size in olivec_sprite_copy_bilinear()
+    if (w <= 0) return;
+    if (h <= 0) return;
+
+    Olivec_Normalized_Rect nr = {0};
+    if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return;
+
+    for (int y = nr.y1; y <= nr.y2; ++y) {
+        for (int x = nr.x1; x <= nr.x2; ++x) {
+            size_t nx = (x - nr.ox1)*sprite.width;
+            size_t ny = (y - nr.oy1)*sprite.height;
+
+            int px = nx%w;
+            int py = ny%h;
+
+            int x1 = nx/w, x2 = nx/w;
+            int y1 = ny/h, y2 = ny/h;
+            if (px < w/2) {
+                // left
+                px += w/2;
+                x1 -= 1;
+                if (x1 < 0) x1 = 0;
+            } else {
+                // right
+                px -= w/2;
+                x2 += 1;
+                if ((size_t) x2 >= sprite.width) x2 = sprite.width - 1;
+            }
+
+            if (py < h/2) {
+                // top
+                py += h/2;
+                y1 -= 1;
+                if (y1 < 0) y1 = 0;
+            } else {
+                // bottom
+                py -= h/2;
+                y2 += 1;
+                if ((size_t) y2 >= sprite.height) y2 = sprite.height - 1;
+            }
+
+            OLIVEC_PIXEL(oc, x, y) =
+                mix_colors2(mix_colors2(OLIVEC_PIXEL(sprite, x1, y1),
+                                        OLIVEC_PIXEL(sprite, x2, y1),
+                                        px, w),
+                            mix_colors2(OLIVEC_PIXEL(sprite, x1, y2),
+                                        OLIVEC_PIXEL(sprite, x2, y2),
+                                        px, w),
+                            py, h);
+        }
+    }
+}
+
 #endif // OLIVEC_IMPLEMENTATION
 
 // TODO: Benchmarking

+ 13 - 1
test.c

@@ -592,6 +592,17 @@ Olivec_Canvas test_triangle_order_flip(void)
     return dst;
 }
 
+Olivec_Canvas test_bilinear_interpolation(void)
+{
+    size_t factor = 2;
+    Olivec_Canvas src = olivec_canvas(tsodinPog_pixels, tsodinPog_width, tsodinPog_height, tsodinPog_width);
+    Olivec_Canvas dst = canvas_alloc(src.width*factor*2, src.height*factor);
+    olivec_fill(dst, RED_COLOR);
+    olivec_sprite_copy(dst, 0, 0, src.width*factor, src.height*factor, src);
+    olivec_sprite_copy_bilinear(dst, src.width*factor, 0, src.width*factor, src.height*factor, src);
+    return dst;
+}
+
 Test_Case test_cases[] = {
     DEFINE_TEST_CASE(fill_rect),
     DEFINE_TEST_CASE(fill_circle),
@@ -614,7 +625,8 @@ Test_Case test_cases[] = {
     DEFINE_TEST_CASE(sprite_blend_null),
     DEFINE_TEST_CASE(sprite_blend_vs_copy),
     DEFINE_TEST_CASE(triangle_order_flip),
-    DEFINE_TEST_CASE(barycentric_overflow)
+    DEFINE_TEST_CASE(barycentric_overflow),
+    DEFINE_TEST_CASE(bilinear_interpolation)
 };
 #define TEST_CASES_COUNT (sizeof(test_cases)/sizeof(test_cases[0]))
 

BIN
test/bilinear_interpolation_expected.png