/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "applib/graphics/graphics.h" #include "applib/graphics/bitblt.h" #include "applib/graphics/bitblt_private.h" #include "applib/graphics/8_bit/framebuffer.h" #include "clar.h" #include "util.h" #include #include // Stubs //////////////////////////////////// #include "graphics_common_stubs.h" #include "stubs_applib_resource.h" #include "test_graphics.h" static GContext ctx; static FrameBuffer framebuffer; // Helpers //////////////////////////////////// static void prv_set_opacity(GBitmap *bmp, uint8_t opacity) { GRect bounds = bmp->bounds; for (uint32_t idx = 0; idx < bounds.size.w * bounds.size.h; idx++) { ((GColor *)(bmp->addr + idx))->a = opacity; } } static GColor prv_next_color(GColor color) { GColor8 result = (GColor8){.argb = (color.argb + 1) % 64}; result.a = color.a; return result; } // Tests //////////////////////////////////// // setup and teardown void test_bitblt__initialize(void) { framebuffer_init(&framebuffer, &(GSize) {DISP_COLS, DISP_ROWS}); test_graphics_context_init(&ctx, &framebuffer); } void test_bitblt__cleanup(void) { } // Test images reside in "tests/fw/graphics/test_images/". // The wscript will convert them from PNGs in that directory to PBIs in the build directory. // Naming conventions of these images tends to be '..png'. // For example: // test_bitblt__8bit_assign would have: // - test_bitblt__8bit_assign.8bit.png // - test_bitblt__8bit_assign-expect.8bit.png // Tests assign, from same size to same size. // Setup: // - Source is 10x10, white. // - Dest is 10x10, green. // Result: // - All white. void test_bitblt__8bit_compop(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_assign.8bit.pbi"); uint8_t dest_data[src_bitmap->bounds.size.w*src_bitmap->bounds.size.h]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = src_bitmap->bounds.size.w, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = src_bitmap->bounds }; // All compositing modes except GCompOpSet should be the same as GCompAssign memset(dest_data, GColorGreen.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi")); memset(dest_data, GColorGreen.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssignInverted, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi")); memset(dest_data, GColorGreen.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpOr, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi")); memset(dest_data, GColorGreen.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAnd, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi")); memset(dest_data, GColorGreen.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpClear, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi")); memset(dest_data, GColorGreen.argb, sizeof(dest_data)); // Update source bitmap to be semi-transparent uint8_t *src_bitmap_buff = src_bitmap->addr; for (int index = 0; index < src_bitmap->bounds.size.h * src_bitmap->row_size_bytes; index++) { src_bitmap_buff[index] = src_bitmap_buff[index] & 0xbf; } bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpSet, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_set-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Test GCompOpTint, from same size to size. // Setup: // - Source is a 10x10 square, white. // - Destination is either a black or white square. // Result: // - When source is transparent or tint color is clear, dest is black. // - When source is opaque, dest is blended blue void test_bitblt__8bit_to_8bit_comptint(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_assign.8bit.pbi"); GRect bounds = gbitmap_get_bounds(src_bitmap); uint8_t dest_data[bounds.size.w * bounds.size.h]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = bounds.size.w, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = src_bitmap->bounds }; uint8_t expect_bmp_data[bounds.size.w * bounds.size.h]; GBitmap expect_bmp = { .addr = expect_bmp_data, .row_size_bytes = bounds.size.w, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = src_bitmap->bounds }; // Verify that the compositing mode is correctly applied when the source is opaque and // the tint color is not clear. memset(dest_data, GColorClear.argb, sizeof(dest_data)); memset(expect_bmp_data, GColorBlue.argb, sizeof(expect_bmp_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpTint, GColorBlue); cl_assert(gbitmap_eq(&dest_bitmap, &expect_bmp, "test_bitblt__8bit_comptint-expect.8bit.pbi")); // Rewrite the destination bitmap to be all black. memset(dest_data, GColorBlack.argb, sizeof(dest_data)); // Verify that if the tint color is clear, than the source bitmap should not affect the destination bitmap memset(expect_bmp_data, GColorBlack.argb, sizeof(expect_bmp_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpTint, GColorClear); cl_assert(gbitmap_eq(&dest_bitmap, &expect_bmp, "test_bitblt__8bit_comptint_clear-expect.8bit.pbi")); // Verify that if the source is transparent, it should not affect the destination bitmap prv_set_opacity(src_bitmap, 0); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpTint, GColorRed); cl_assert(gbitmap_eq(&dest_bitmap, &expect_bmp, "test_bitblt__8bit_comptint_clear-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Tests comptint, multiple tint colors and varying opacity of the source image. // Setup: // - Destination is two 128 by 64 blocks expressing the 64 colors; each row expresses // one of the colors stack vertically. // - Source is a set of 4 by 4 blocks each with a 1 pixel wide vertical strip of a color // with an opacity in [0,3] inclusive. // Result: // - Destination should be blended properly void test_bitblt__8bit_comptint_blend(void) { const uint8_t NUM_COLORS = 64; const uint8_t WIDTH = NUM_COLORS * 2; const uint8_t LEGEND_WIDTH = 4; const uint8_t LEGEND_HEIGHT = 4; const uint8_t OFFSET = 1; const uint8_t HEIGHT = WIDTH; const uint8_t NUM_OPACITIES = 4; const uint8_t TOTAL_WIDTH = LEGEND_WIDTH * 2 + WIDTH; const uint8_t TOTAL_HEIGHT = HEIGHT + OFFSET + LEGEND_HEIGHT * 2; uint8_t dest_data[TOTAL_WIDTH * TOTAL_HEIGHT]; memset(dest_data, GColorWhite.argb, sizeof(dest_data)); GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = TOTAL_WIDTH, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = (GRect){GPointZero, (GSize){TOTAL_WIDTH, TOTAL_HEIGHT}} }; GColor color = (GColor){ .a = 3, .r = 0, .g = 0, .b = 0 }; uint8_t src_data[] = { color.argb }; GBitmap src_bmp = (GBitmap){ .addr = src_data, .row_size_bytes = 1, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = GRect(0, 0, 1, 1) }; for (uint8_t offset_y = 0; offset_y < NUM_COLORS; offset_y++) { const int16_t offset_x = OFFSET + LEGEND_WIDTH + WIDTH; const int16_t x = LEGEND_WIDTH; memset(src_data, color.argb, sizeof(src_data)); color = prv_next_color(color); const int16_t y_upper = LEGEND_HEIGHT + offset_y; const GRect upper_rect = GRect(x, y_upper, WIDTH, 1); const GRect upper_left_legend_rect = GRect(0, y_upper, 3, 1); const GRect upper_right_legend_rect = GRect(offset_x, y_upper, 3, 1); bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, upper_rect, GPointZero, GCompOpAssign, GColorWhite); bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, upper_left_legend_rect, GPointZero, GCompOpAssign, GColorWhite); bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, upper_right_legend_rect, GPointZero, GCompOpAssign, GColorWhite); const int16_t y_lower = y_upper + LEGEND_HEIGHT + NUM_COLORS + OFFSET; const GRect lower_rect = GRect(x, y_lower, WIDTH, 1); const GRect lower_left_legend_rect = GRect(0, y_lower, 3, 1); const GRect lower_right_legend_rect = GRect(offset_x, y_lower, 3, 1); bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, lower_rect, GPointZero, GCompOpAssign, GColorWhite); bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, lower_left_legend_rect, GPointZero, GCompOpAssign, GColorWhite); bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, lower_right_legend_rect, GPointZero, GCompOpAssign, GColorWhite); } // RGB value should be discarded later on adding them here might relveal bugs. // .a is the important part GColor8 test_blend_colors[] = { (GColor8){.a = 0, .r = 3, .g = 2, .b = 1}, (GColor8){.a = 1, .r = 0, .g = 3, .b = 2}, (GColor8){.a = 2, .r = 1, .g = 0, .b = 3}, (GColor8){.a = 3, .r = 2, .g = 1, .b = 0} }; // Test image with four pixels of all our suported alpha values GBitmap test_bmp = (GBitmap){ .addr = test_blend_colors, .row_size_bytes = 4, .bounds = GRect(0, 0, 4, 1), .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT }; for (uint8_t rgb_half = 0; rgb_half < NUM_COLORS / 2; rgb_half++) { const int16_t x = rgb_half * NUM_OPACITIES + LEGEND_WIDTH; const int8_t legend_height = 3; // Upper row with destination colors from 0..31 const GColor upper_tint_color = (GColor){.argb = 0b11000000 | rgb_half}; const int16_t y_upper = LEGEND_HEIGHT; const GRect upper_rect = GRect(x, y_upper, test_bmp.bounds.size.w, NUM_COLORS); const GRect upper_legend_rect = GRect(x, y_upper - LEGEND_HEIGHT, test_bmp.bounds.size.w, legend_height); GColor8 upper_legend[4] = { [0 ... 3] = upper_tint_color }; test_bmp.addr = upper_legend; bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, upper_legend_rect, GPointZero, GCompOpAssign, GColorWhite); test_bmp.addr = test_blend_colors; bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, upper_rect, GPointZero, GCompOpTint, upper_tint_color); // Lower row with destination colors from 31..63 const int16_t y_lower = y_upper + NUM_COLORS + OFFSET + LEGEND_HEIGHT; const GRect lower_rect = GRect(x, y_lower, test_bmp.bounds.size.w, NUM_COLORS); const GColor lower_tint_color = (GColor){.argb = 0b11000000 | (rgb_half + (NUM_COLORS / 2))}; const GRect lower_legend_rect = GRect(x, y_lower - LEGEND_HEIGHT, test_bmp.bounds.size.w, legend_height); GColor8 lower_legend[4] = { [0 ... 3] = lower_tint_color }; test_bmp.addr = lower_legend; bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, lower_legend_rect, GPointZero, GCompOpAssign, GColorWhite); test_bmp.addr = test_blend_colors; bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, lower_rect, GPointZero, GCompOpTint, lower_tint_color); } cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_comptint_blend-expect.8bit.pbi")); } // Tests assign, clipping, makes sure in bottom right corner. // Setup: // - Source is 10x15, black box ((0, 0), (5, 10)), rest is red. // - Dest is White, 50x50. // - Dest offset is set to 5x10 pixels in bottom right corner. // Result: // - White, with 5x10 black box in bottom right corner. void test_bitblt__8bit_clipping(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_clipping.8bit.pbi"); uint8_t dest_data[50*50]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 50, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 50, 50 } } }; memset(dest_data, GColorWhite.argb, sizeof(dest_data)); GPoint dest_offset = { dest_bitmap.bounds.size.w-5, dest_bitmap.bounds.size.h-10 }; bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, dest_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_clipping-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Test horizontal wrapping when dest_rect wider than src_bitmap. // Setup: // - Source 15 x 10, each row has the folling pattern: // - 2px Red // - 13px Black // - Dest Green 50x50 // - Dest rect (17, 10) at (0, 0) // Result: // - 2px Red // - 13px Black // - 2px Red // - Rest Blue void test_bitblt__8bit_wrap_x(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_wrap_x.8bit.pbi"); uint8_t dest_data[50*50]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 50, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 50, 50 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); // 2 wider than src_bitmap, so 2 columns of red will repeat again. GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w + 2, src_bitmap->bounds.size.h); bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit( &dest_bitmap, src_bitmap, dest_rect, GPointZero, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_wrap_x-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Test vertical wrapping when dest_rect taller than src_bitmap. // Setup: // - Source is 25 x 10 // - 4 rows red, 2 rows blue, 4 rows black. // - Dest is Green, 50 x 50 // - Dest Rect is 10 x 24 at (0, 0) // Result: // - Pattern repeated vertically x2, plus 4 rows of red. void test_bitblt__8bit_wrap_y(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_wrap_y.8bit.pbi"); uint8_t dest_data[50*50]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 50, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 50, 50 } } }; memset(dest_data, GColorGreen.argb, sizeof(dest_data)); GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w, src_bitmap->bounds.size.h*2 + 4); bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit( &dest_bitmap, src_bitmap, dest_rect, GPointZero, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_wrap_y-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Test src_origin_offset, shouldn't see any red in dest_bitmap. // This covers src_origin_offset, y-axis and x-axis wraparound. // Setup: // - Source 25x25, 2 columns, 2 rows red, rest is black. // - Source offset starts at (2, 2) // - Dest is blue, 100x100. // - Dest rect is 50x50 at (0,0). // Result: // - No red in dest_bitmap. // - 50x50 black square at (0,0), rest is blue. void test_bitblt__8bit_src_origin_offset_wrap(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_src_origin_offset_wrap.8bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h*2); GPoint src_origin_offset = { 2, 2 }; // Offset past the 2 red rows bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit( &dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_src_origin_offset_wrap-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // // Test 1-bit to 8-bit blitting /////////////////////////////// // Setup: // - Source is 25x25. // - Source has alternating white / black lines. // - Dest is Blue, 100x100. // - Dest offset is (0,0) to blit to top left corner. // Result: // - 25x25 alternating black / white lines in top left corner. void test_bitblt__1bit_to_8bit_compop(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_assign.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_assign-expect.8bit.pbi")); memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssignInverted, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_assigninverted-expect.8bit.pbi")); memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpOr, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_or-expect.8bit.pbi")); memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAnd, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_and-expect.8bit.pbi")); memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpClear, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_clear-expect.8bit.pbi")); memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpSet, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_set-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is an image of a white cross // - Dest is blue, same size as source // Result: // - Destination should be written with a White cross // Description: // - This test verifies that when the bitmap is 1-bit, we treat white as a // non-transparent color void test_bitblt__1bit_to_8bit_compor(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_compor.1bit.pbi"); GRect bounds = gbitmap_get_bounds(src_bitmap); cl_assert_equal_i(src_bitmap->info.format, GBitmapFormat1Bit); uint8_t dest_data[bounds.size.w * bounds.size.h]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = bounds.size.w, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = src_bitmap->bounds }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpOr, GColorLightGray); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_compor-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is a 1bit image of a white cross with a black background // Result: // - The image names describe the expected result of each destination color / tint color // combination void test_bitblt__1bit_to_8bit_comptint(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_comptint.1bit.pbi"); const GRect bounds = gbitmap_get_bounds(src_bitmap); cl_assert_equal_i(src_bitmap->info.format, GBitmapFormat1Bit); uint8_t dest_data[bounds.size.w * bounds.size.h]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = bounds.size.w, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = src_bitmap->bounds }; // Image name descriptions // comptint__on_.8bit // Destination White memset(dest_data, GColorWhite.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_white_cross_white_corners-expect.8bit.pbi")); memset(dest_data, GColorWhite.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_white_cross_black_corners-expect.8bit.pbi")); memset(dest_data, GColorWhite.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorLightGray); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_white_cross_lightgray_corners-expect.8bit.pbi")); // Destination Black memset(dest_data, GColorBlack.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_black_cross_white_corners-expect.8bit.pbi")); memset(dest_data, GColorBlack.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_black_cross_black_corners-expect.8bit.pbi")); memset(dest_data, GColorBlack.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorLightGray); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_black_cross_lightgray_corners-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is 25x25. // - Source has alternating white / black lines. // - Dest is Blue, 100x100. // - Dest offset is set to 8x10 clipped in the bottom right corner. // Result: // - There should be an 8x10 alternating black & white lines in the bottom right corner. void test_bitblt__1bit_to_8bit_clipping(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_clipping.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); GPoint dest_offset = { dest_bitmap.bounds.size.w-8, dest_bitmap.bounds.size.h-10 }; bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, dest_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_clipping-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is 25 x 25. // - Source rows alternating: 1px black, 1px white. // - Dest is Blue, 100x100 // - Dest rect is 50 x 25, at (0, 0) // Result: // - 50x20 of alternating stripes, rest is blue. void test_bitblt__1bit_to_8bit_wrap_x(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_wrap_x.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h); bitblt_bitmap_into_bitmap_tiled_1bit_to_8bit( &dest_bitmap, src_bitmap, dest_rect, GPointZero, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_wrap_x-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is 40 x 30. // - Source has 2 columns, 4 rows of black, rest is white. // - Dest is all blue. // - Source offset (2, 4) past black. // - Destination is 100 x 100 // - Destination rect is size of white portion of source. // Result: // - Blue bitmap with white square at 0,0 of dest_rect size. void test_bitblt__1bit_to_8bit_src_origin_offset(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_src_origin_offset.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w - 2, src_bitmap->bounds.size.h - 4); GPoint src_origin_offset = { 2, 4 }; // Offset past the black bitblt_bitmap_into_bitmap_tiled_1bit_to_8bit( &dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_src_origin_offset-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is a 10x32 white square. // - Dest is all black. // - Dest origin offset set to 15, 18. // - Dest clipped to 10x10 // Result: // - Black bitmap with 10x10 white square starting at (15, 10) void test_bitblt__1bit_to_8bit_dest_origin_offset_clip(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_dest_origin_offset_clip.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlack.argb, sizeof(dest_data)); GRect dest_rect = GRect(15, 10, 10, 10); GPoint src_origin_offset = { 0, 0 }; bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_dest_origin_offset_clip-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source width is 32 pixels (ie. a word in source) // - Source starts with 2 rows and 4 columns of black pixels. // - Dest is all blue. // - Src origin is set to (4, 2) // - dest origin is set to 10, 25 // - dest size is twice the height / width of the source. void test_bitblt__1bit_to_8bit_src_origin_offset_wrap(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_src_origin_offset_wrap.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); GRect dest_rect = GRect(10, 25, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h*2); GPoint src_origin_offset = { .x = 4, .y = 2 }; // Offset past the black bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_src_origin_offset_wrap-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source width is not a multiple of 8 (ie. not byte aligned in source) // - Source starts with 2 rows and 4 columns of black pixels. // - Dest is all blue. // - Src origin is set to (4, 2) // - dest origin is set to 22, 6 // - dest size is twice the height / width of the source. void test_bitblt__1bit_to_8bit_src_origin_offset_wrap2(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_src_origin_offset_wrap2.1bit.pbi"); uint8_t dest_data[100*100]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 100, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 100, 100 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); GRect dest_rect = GRect(21, 6, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h*2); GPoint src_origin_offset = { .x = 4, .y = 2 }; // Offset past the black bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_src_origin_offset_wrap2-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source has 2 lines of white and the rest black. // - Destination all blue. // - Use gbitmap_init_as_sub_bitmap to get a sub-bitmap that starts at y = 2 // Result: // - A 48 x 50 black box starting at y=2 in dest, rest is blue. void test_bitblt__bitmap_into_bitmap_sub_bitmap(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__bitmap_into_bitmap_sub_bitmap.8bit.pbi"); GBitmap cropped_src_bitmap; gbitmap_init_as_sub_bitmap(&cropped_src_bitmap, src_bitmap, (GRect) { { 0, 2 }, src_bitmap->bounds.size }); uint8_t dest_data[50*50]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 50, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 50, 50 } } }; memset(dest_data, GColorBlue.argb, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, &cropped_src_bitmap, (GPoint) { 0, 2 }, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__bitmap_into_bitmap_sub_bitmap-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Test: // - source origin offset // - source bounds origin and size // - wrapping into larger destination // - dest rect not at { 0, 0 } // Setup: // - Source has non-zero bounds origin, { 5, 5 }, outside of this is red. // - Source has non-full bounds size, { 10, 10 }, outside of this is white. // - Source has an origin offset, { 3, 6 }, outside of this is blue. // - Rest is black // // - Destination has non-zero dest_rect origin, { 4, 4 } // - Destination has non-full dest_rect size larger than source, { 10, 10 } // - Destination has full size of { 20, 20 } // Result: // - Expect a 10 x 10 black box at { 4, 4 } that is of size { 10, 10 } // - Remainder should be green. void test_bitblt__8bit_bounds_and_origin_offset(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__bounds_and_origin_offset.8bit.pbi"); src_bitmap->bounds = (GRect) { { 5, 5}, { 10, 10 } }; uint8_t dest_data[50*50]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 50, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 50, 50 } } }; memset(dest_data, GColorGreen.argb, sizeof(dest_data)); const GRect dest_rect = GRect(4, 4, 10, 10); const GPoint src_origin_offset = GPoint(3, 6); bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__bounds_and_origin_offset-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // Setup: // - Source is a 1bit image of a white cross with a black background // Result: // - The image names describe the expected result of each destination color / tint color // combination void test_bitblt__1bit_to_1bit_comptint(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_1bit_comptint.1bit.pbi"); const GRect bounds = gbitmap_get_bounds(src_bitmap); cl_assert_equal_i(src_bitmap->info.format, GBitmapFormat1Bit); uint8_t dest_data[bounds.size.w * bounds.size.h]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = src_bitmap->row_size_bytes, .info.format = GBitmapFormat1Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = src_bitmap->bounds }; // Image name descriptions // comptint__on_.1bit // Destination White memset(dest_data, 0b11111111, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorClear); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_white_on_white-expect.1bit.pbi")); memset(dest_data, 0b11111111, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_white_on_white-expect.1bit.pbi")); memset(dest_data, 0b11111111, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_black_on_white-expect.1bit.pbi")); // Destination Black memset(dest_data, 0b00000000, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorClear); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_black_on_black-expect.1bit.pbi")); memset(dest_data, 0b00000000, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_white_on_black-expect.1bit.pbi")); memset(dest_data, 0b00000000, sizeof(dest_data)); bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack); cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_black_on_black-expect.1bit.pbi")); gbitmap_destroy(src_bitmap); } // Test: // - source origin offset // - source bounds origin and size // - Source bounds origin is beyond (32, y) to pass word boundary. // - Source origin offset is beyond (32, y) to pass another word boundary. // - wrapping into larger destination // - dest rect not at { 0, 0 } // Setup: // - Dest rect at {4, 4}, repeat twice and a bit: {140, 55} // - Source bounds origin at {37, 3), size {63, 23} // - Source origin offset (39, 11) void DISABLED_test_bitblt__1bit_to_8bit_bounds_and_origin_offset(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_bounds_and_origin_offset.1bit.pbi"); uint8_t dest_data[144*168]; GBitmap dest_bitmap = { .addr = dest_data, .row_size_bytes = 144, .info.format = GBitmapFormat8Bit, .info.version = GBITMAP_VERSION_CURRENT, .bounds = { { 0, 0 }, { 144, 168 } } }; memset(dest_data, GColorGreen.argb, sizeof(dest_data)); const GRect dest_rect = GRect(4, 4, 140, 55); const GPoint src_origin_offset = GPoint(39, 11); src_bitmap->bounds = (GRect) { { 37, 3}, { 63, 23 } }; bitblt_bitmap_into_bitmap_tiled_1bit_to_8bit(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_bounds_and_origin_offset-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); } // This test is the same as test_bitblt__1bit_to_1bit_bounds_and_origin_offset, except it is for // 1-bit to 1-bit. // FIXME: This is a known legacy-broken case in 1-bit bitblt. See PBL-14671 for more information void DISABLED__test_bitblt__1bit_to_1bit_bounds_and_origin_offset(void) { GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_1bit_bounds_and_origin_offset.1bit.pbi"); uint8_t dest_data[src_bitmap->row_size_bytes * src_bitmap->bounds.size.h]; GBitmap dest_bitmap = *src_bitmap; dest_bitmap.addr = dest_data; dest_bitmap.bounds = src_bitmap->bounds; memset(dest_data, 0xff, sizeof(dest_data)); const GRect dest_rect = GRect(4, 4, 140, 55); const GPoint src_origin_offset = GPoint(39, 11); src_bitmap->bounds = (GRect) { { 37, 3}, { 63, 23 } }; bitblt_bitmap_into_bitmap_tiled_1bit_to_1bit(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite); cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_bounds_and_origin_offset-expect.8bit.pbi")); gbitmap_destroy(src_bitmap); }