Browse Source

Implement alpha blending

rexim 3 years ago
parent
commit
51f17a9a9d
3 changed files with 71 additions and 13 deletions
  1. 56 12
      olive.c
  2. 15 1
      test.c
  3. BIN
      test/test_alpha_blending_expected.png

+ 56 - 12
olive.c

@@ -55,6 +55,51 @@ bool olivec_normalize_rect(int x, int y, int w, int h,
     return true;
     return true;
 }
 }
 
 
+typedef enum {
+    COMP_RED = 0,
+    COMP_GREEN,
+    COMP_BLUE,
+    COMP_ALPHA,
+    COUNT_COMPS,
+} Comp_Index;
+
+void unpack_rgba32(uint32_t c, uint8_t comp[COUNT_COMPS])
+{
+    for (size_t i = 0; i < COUNT_COMPS; ++i) {
+        comp[i] = c&0xFF;
+        c >>= 8;
+    }
+}
+
+uint32_t pack_rgba32(uint8_t comp[COUNT_COMPS])
+{
+    uint32_t result = 0;
+    for (size_t i = 0; i < COUNT_COMPS; ++i) {
+        result |= comp[i]<<(8*i);
+    }
+    return result;
+}
+
+uint8_t olivec_mix_comps(uint16_t c1, uint16_t c2, uint16_t a)
+{
+    return c1 + (c2 - c1)*a/255;
+}
+
+uint32_t olivec_mix_colors(uint32_t c1, uint32_t c2)
+{
+    uint8_t comp1[COUNT_COMPS];
+    unpack_rgba32(c1, comp1);
+
+    uint8_t comp2[COUNT_COMPS];
+    unpack_rgba32(c2, comp2);
+
+    for (size_t i = 0; i < COMP_ALPHA; ++i) {
+        comp1[i] = olivec_mix_comps(comp1[i], comp2[i], comp2[COMP_ALPHA]);
+    }
+
+    return pack_rgba32(comp1);
+}
+
 void olivec_fill_rect(uint32_t *pixels, size_t pixels_width, size_t pixels_height,
 void olivec_fill_rect(uint32_t *pixels, size_t pixels_width, size_t pixels_height,
                       int x, int y, int w, int h,
                       int x, int y, int w, int h,
                       uint32_t color)
                       uint32_t color)
@@ -64,7 +109,7 @@ void olivec_fill_rect(uint32_t *pixels, size_t pixels_width, size_t pixels_heigh
 
 
     for (int y = y1; y <= y2; ++y) {
     for (int y = y1; y <= y2; ++y) {
         for (int x = x1; x <= x2; ++x) {
         for (int x = x1; x <= x2; ++x) {
-            pixels[y*pixels_width + x] = color;
+            pixels[y*pixels_width + x] = olivec_mix_colors(pixels[y*pixels_width + x], color);
         }
         }
     }
     }
 }
 }
@@ -82,7 +127,7 @@ void olivec_fill_circle(uint32_t *pixels, size_t pixels_width, size_t pixels_hei
             int dx = x - cx;
             int dx = x - cx;
             int dy = y - cy;
             int dy = y - cy;
             if (dx*dx + dy*dy <= r*r) {
             if (dx*dx + dy*dy <= r*r) {
-                pixels[y*pixels_width + x] = color;
+                pixels[y*pixels_width + x] = olivec_mix_colors(pixels[y*pixels_width + x], color);
             }
             }
         }
         }
     }
     }
@@ -109,7 +154,7 @@ void olivec_draw_line(uint32_t *pixels, size_t pixels_width, size_t pixels_heigh
                 if (sy1 > sy2) OLIVEC_SWAP(int, sy1, sy2);
                 if (sy1 > sy2) OLIVEC_SWAP(int, sy1, sy2);
                 for (int y = sy1; y <= sy2; ++y) {
                 for (int y = sy1; y <= sy2; ++y) {
                     if (0 <= y && y < (int) pixels_height) {
                     if (0 <= y && y < (int) pixels_height) {
-                        pixels[y*pixels_width + x] = color;
+                        pixels[y*pixels_width + x] = olivec_mix_colors(pixels[y*pixels_width + x], color);
                     }
                     }
                 }
                 }
             }
             }
@@ -120,14 +165,14 @@ void olivec_draw_line(uint32_t *pixels, size_t pixels_width, size_t pixels_heigh
             if (y1 > y2) OLIVEC_SWAP(int, y1, y2);
             if (y1 > y2) OLIVEC_SWAP(int, y1, y2);
             for (int y = y1; y <= y2; ++y) {
             for (int y = y1; y <= y2; ++y) {
                 if (0 <= y && y < (int) pixels_height) {
                 if (0 <= y && y < (int) pixels_height) {
-                    pixels[y*pixels_width + x] = color;
+                    pixels[y*pixels_width + x] = olivec_mix_colors(pixels[y*pixels_width + x], color);
                 }
                 }
             }
             }
         }
         }
     }
     }
 }
 }
 
 
-void olivec_fill_triangle(uint32_t *pixels, size_t width, size_t height,
+void olivec_fill_triangle(uint32_t *pixels, size_t pixels_width, size_t pixels_height,
                           int x1, int y1,
                           int x1, int y1,
                           int x2, int y2,
                           int x2, int y2,
                           int x3, int y3,
                           int x3, int y3,
@@ -155,13 +200,13 @@ void olivec_fill_triangle(uint32_t *pixels, size_t width, size_t height,
 
 
     for (int y = y1; y <= y2; ++y) {
     for (int y = y1; y <= y2; ++y) {
         // TODO: move boundary checks outside of loops in olivec_fill_triangle
         // TODO: move boundary checks outside of loops in olivec_fill_triangle
-        if (0 <= y && (size_t) y < height) {
+        if (0 <= y && (size_t) y < pixels_height) {
             int s1 = dy12 != 0 ? (y - y1)*dx12/dy12 + x1 : x1;
             int s1 = dy12 != 0 ? (y - y1)*dx12/dy12 + x1 : x1;
             int s2 = dy13 != 0 ? (y - y1)*dx13/dy13 + x1 : x1;
             int s2 = dy13 != 0 ? (y - y1)*dx13/dy13 + x1 : x1;
             if (s1 > s2) OLIVEC_SWAP(int, s1, s2);
             if (s1 > s2) OLIVEC_SWAP(int, s1, s2);
             for (int x = s1; x <= s2; ++x) {
             for (int x = s1; x <= s2; ++x) {
-                if (0 <= x && (size_t) x < width) {
-                    pixels[y*width + x] = color;
+                if (0 <= x && (size_t) x < pixels_width) {
+                    pixels[y*pixels_width + x] = olivec_mix_colors(pixels[y*pixels_width + x], color);
                 }
                 }
             }
             }
         }
         }
@@ -173,13 +218,13 @@ void olivec_fill_triangle(uint32_t *pixels, size_t width, size_t height,
     int dy31 = y1 - y3;
     int dy31 = y1 - y3;
 
 
     for (int y = y2; y <= y3; ++y) {
     for (int y = y2; y <= y3; ++y) {
-        if (0 <= y && (size_t) y < height) {
+        if (0 <= y && (size_t) y < pixels_height) {
             int s1 = dy32 != 0 ? (y - y3)*dx32/dy32 + x3 : x3;
             int s1 = dy32 != 0 ? (y - y3)*dx32/dy32 + x3 : x3;
             int s2 = dy31 != 0 ? (y - y3)*dx31/dy31 + x3 : x3;
             int s2 = dy31 != 0 ? (y - y3)*dx31/dy31 + x3 : x3;
             if (s1 > s2) OLIVEC_SWAP(int, s1, s2);
             if (s1 > s2) OLIVEC_SWAP(int, s1, s2);
             for (int x = s1; x <= s2; ++x) {
             for (int x = s1; x <= s2; ++x) {
-                if (0 <= x && (size_t) x < width) {
-                    pixels[y*width + x] = color;
+                if (0 <= x && (size_t) x < pixels_width) {
+                    pixels[y*pixels_width + x] = olivec_mix_colors(pixels[y*pixels_width + x], color);
                 }
                 }
             }
             }
         }
         }
@@ -187,7 +232,6 @@ void olivec_fill_triangle(uint32_t *pixels, size_t width, size_t height,
 }
 }
 
 
 // TODO: Olivec_Canvas
 // TODO: Olivec_Canvas
-// TODO: Alpha blending
 // TODO: supersampling
 // TODO: supersampling
 // TODO: olivec_draw_circle
 // TODO: olivec_draw_circle
 // TODO: olivec_(draw|fill)_ellipse
 // TODO: olivec_(draw|fill)_ellipse

+ 15 - 1
test.c

@@ -128,7 +128,7 @@ Replay_Result replay_test_case(const char *program_path, const char *expected_fi
                 fprintf(stderr, "ERROR: could not generate diff image %s: %s\n", diff_file_path, strerror(errno));
                 fprintf(stderr, "ERROR: could not generate diff image %s: %s\n", diff_file_path, strerror(errno));
                 return_defer(REPLAY_ERRORED);
                 return_defer(REPLAY_ERRORED);
             }
             }
-            
+
             fprintf(stderr, "%s: HINT: See actual image %s\n", expected_file_path, actual_file_path);
             fprintf(stderr, "%s: HINT: See actual image %s\n", expected_file_path, actual_file_path);
             fprintf(stderr, "%s: HINT: See diff image %s\n", expected_file_path, diff_file_path);
             fprintf(stderr, "%s: HINT: See diff image %s\n", expected_file_path, diff_file_path);
             fprintf(stderr, "%s: HINT: If this behaviour is intentional confirm that by updating the image with `$ %s record`\n", expected_file_path, program_path);
             fprintf(stderr, "%s: HINT: If this behaviour is intentional confirm that by updating the image with `$ %s record`\n", expected_file_path, program_path);
@@ -208,11 +208,25 @@ void test_fill_triangle(void)
     }
     }
 }
 }
 
 
+void test_alpha_blending(void)
+{
+    olivec_fill(actual_pixels, WIDTH, HEIGHT, BACKGROUND_COLOR);
+    olivec_fill_rect(actual_pixels, WIDTH, HEIGHT, 0, 0, WIDTH*3/4, HEIGHT*3/4, RED_COLOR);
+    olivec_fill_rect(actual_pixels, WIDTH, HEIGHT, WIDTH-1, HEIGHT-1, -WIDTH*3/4, -HEIGHT*3/4, 0x5520AA20);
+    olivec_fill_circle(actual_pixels, WIDTH, HEIGHT, WIDTH/2, HEIGHT/2, WIDTH/4, 0xBBAA2020);
+    olivec_fill_triangle(actual_pixels, WIDTH, HEIGHT,
+                         0,     HEIGHT,
+                         WIDTH, HEIGHT,
+                         WIDTH/2, 0,
+                         0xBB20AAAA);
+}
+
 Test_Case test_cases[] = {
 Test_Case test_cases[] = {
     DEFINE_TEST_CASE(test_fill_rect),
     DEFINE_TEST_CASE(test_fill_rect),
     DEFINE_TEST_CASE(test_fill_circle),
     DEFINE_TEST_CASE(test_fill_circle),
     DEFINE_TEST_CASE(test_draw_line),
     DEFINE_TEST_CASE(test_draw_line),
     DEFINE_TEST_CASE(test_fill_triangle),
     DEFINE_TEST_CASE(test_fill_triangle),
+    DEFINE_TEST_CASE(test_alpha_blending),
 };
 };
 #define TEST_CASES_COUNT (sizeof(test_cases)/sizeof(test_cases[0]))
 #define TEST_CASES_COUNT (sizeof(test_cases)/sizeof(test_cases[0]))
 
 

BIN
test/test_alpha_blending_expected.png