pebble/tests/fw/graphics/test_gbitmap_processor.c
2025-01-27 11:38:16 -08:00

302 lines
13 KiB
C

/*
* 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 "clar.h"
#include "applib/graphics/framebuffer.h"
// Stubs
///////////////////////
#include "stubs_app_state.h"
#include "stubs_graphics_circle.h"
#include "stubs_graphics_line.h"
#include "stubs_graphics_private.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_process_manager.h"
const GDrawRawImplementation g_default_draw_implementation;
// Statics
///////////////////////
static GContext s_ctx;
static FrameBuffer s_fb;
typedef struct MockBitbltBitmapIntoBitmapTiledCallRecording {
GBitmap *dest_bitmap;
const GBitmap *src_bitmap;
GRect dest_rect;
GPoint src_origin_offset;
GCompOp compositing_mode;
GColor8 tint_color;
} MockBitbltBitmapIntoBitmapTiledCallRecording;
typedef struct MockBitbltBitmapIntoBitmapTiledCallRecordings {
unsigned int call_count;
MockBitbltBitmapIntoBitmapTiledCallRecording last_call;
} MockBitbltBitmapIntoBitmapTiledCallRecordings;
static MockBitbltBitmapIntoBitmapTiledCallRecordings s_bitblt_bitmap_into_bitmap_tiled_calls;
// Fakes
///////////////////////
GContext *graphics_context_get_current_context(void) {
return &s_ctx;
}
void bitblt_bitmap_into_bitmap_tiled(GBitmap* dest_bitmap, const GBitmap *src_bitmap,
GRect dest_rect, GPoint src_origin_offset,
GCompOp compositing_mode, GColor8 tint_color) {
s_bitblt_bitmap_into_bitmap_tiled_calls.call_count++;
s_bitblt_bitmap_into_bitmap_tiled_calls.last_call =
(MockBitbltBitmapIntoBitmapTiledCallRecording) {
.dest_bitmap = dest_bitmap,
.src_bitmap = src_bitmap,
.dest_rect = dest_rect,
.src_origin_offset = src_origin_offset,
.compositing_mode = compositing_mode,
.tint_color = tint_color,
};
}
// Helpers
///////////////////////
static void cl_assert_equal_rect(const GRect a, const GRect b) {
cl_assert_equal_i(a.origin.x, b.origin.x);
cl_assert_equal_i(a.origin.y, b.origin.y);
cl_assert_equal_i(a.size.w, b.size.w);
cl_assert_equal_i(a.size.h, b.size.h);
}
// Setup
///////////////////////
void test_gbitmap_processor__initialize(void) {
s_fb = (FrameBuffer) {};
framebuffer_init(&s_fb, &(GSize) {DISP_COLS, DISP_ROWS});
graphics_context_init(&s_ctx, &s_fb, GContextInitializationMode_App);
s_bitblt_bitmap_into_bitmap_tiled_calls = (MockBitbltBitmapIntoBitmapTiledCallRecordings) {};
}
void test_gbitmap_processor__cleanup(void) {
}
// Tests
///////////////////////
#define EXPECTED_RECT_IN_PRE_FUNCTION (GRect(4, 3, 2, 1))
void test_gbitmap_processor__null_arguments(void) {
GBitmap bitmap = {0};
const GRect rect = EXPECTED_RECT_IN_PRE_FUNCTION;
// Passing NULL for the processor shouldn't cause any problems
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, NULL);
// And it should try to draw the bitmap
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 1);
// Passing a processor with NULL functions shouldn't cause any problems
GBitmapProcessor processor = {0};
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, &processor);
// And it should once again try to draw the bitmap
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 2);
}
#define EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION (GCompOpSet)
#define EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION (GColorShockingPink)
#define COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION (GCompOpTint)
#define TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION (GColorTiffanyBlue)
#define RECT_TO_SPECIFY_IN_PRE_FUNCTION (GRect(-50, -50, 100, 100))
#define BITMAP_TO_SPECIFY_IN_PRE_FUNCTION ((GBitmap *)1234)
#define EXPECTED_CLIPPED_RECT_AFTER_DRAWING_BITMAP (GRect(0, 0, 50, 50))
typedef struct PreAndPostFunctionsTestProcessor {
GBitmapProcessor processor;
GCompOp previous_compositing_mode;
GColor previous_tint_color;
} PreAndPostFunctionsTestProcessor;
static void prv_pre_and_post_functions__pre(GBitmapProcessor *processor, GContext *ctx,
const GBitmap **bitmap_to_use,
GRect *global_grect_to_use) {
PreAndPostFunctionsTestProcessor *processor_with_data =
(PreAndPostFunctionsTestProcessor *)processor;
// Record the existing compositing mode and tint color and check that they are what we expect
cl_assert(ctx->draw_state.compositing_mode ==
EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION);
processor_with_data->previous_compositing_mode = ctx->draw_state.compositing_mode;
cl_assert(gcolor_equal(ctx->draw_state.tint_color,
EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION));
processor_with_data->previous_tint_color = ctx->draw_state.tint_color;
// Set the compositing mode and tint color to different values
ctx->draw_state.compositing_mode = COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION;
ctx->draw_state.tint_color = TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION;
// Check that the rect here is what we gave to graphics_draw_bitmap_in_rect_processed()
cl_assert_equal_rect(*global_grect_to_use, EXPECTED_RECT_IN_PRE_FUNCTION);
// Change the rect
*global_grect_to_use = RECT_TO_SPECIFY_IN_PRE_FUNCTION;
// Change the bitmap
*bitmap_to_use = BITMAP_TO_SPECIFY_IN_PRE_FUNCTION;
}
static void prv_pre_and_post_functions__post(GBitmapProcessor *processor, GContext *ctx,
const GBitmap *bitmap_used,
const GRect *global_clipped_grect_used) {
PreAndPostFunctionsTestProcessor *processor_with_data =
(PreAndPostFunctionsTestProcessor *)processor;
// Check that the changes made to the GContext in .pre are still present
cl_assert(ctx->draw_state.compositing_mode == COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION);
cl_assert(gcolor_equal(ctx->draw_state.tint_color, TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION));
// Reverse the changes to the GContext that were made in .pre
ctx->draw_state.compositing_mode = processor_with_data->previous_compositing_mode;
ctx->draw_state.tint_color = processor_with_data->previous_tint_color;
// Check that the bitmap here is the bitmap we specified in the .pre function
cl_assert_equal_p(bitmap_used, BITMAP_TO_SPECIFY_IN_PRE_FUNCTION);
// Check that the rect here is the clipped version of the rect we specified in the .pre function
cl_assert_equal_rect(*global_clipped_grect_used, EXPECTED_CLIPPED_RECT_AFTER_DRAWING_BITMAP);
}
void test_gbitmap_processor__pre_and_post_functions(void) {
GBitmap bitmap = {0};
const GRect rect = EXPECTED_RECT_IN_PRE_FUNCTION;
// Set the compositing mode and tint color to known values
s_ctx.draw_state.compositing_mode = EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION;
s_ctx.draw_state.tint_color = EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION;
PreAndPostFunctionsTestProcessor processor = (PreAndPostFunctionsTestProcessor) {
.processor.pre = prv_pre_and_post_functions__pre,
.processor.post = prv_pre_and_post_functions__post,
};
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, &processor.processor);
// Check that the bitmap was drawn
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 1);
// Check that the modifications made in the .pre function propagated to the bitmap drawing
cl_assert(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.compositing_mode ==
COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION);
cl_assert(gcolor_equal(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.tint_color,
TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION));
cl_assert_equal_rect(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.dest_rect,
EXPECTED_CLIPPED_RECT_AFTER_DRAWING_BITMAP);
cl_assert_equal_p(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.src_bitmap,
BITMAP_TO_SPECIFY_IN_PRE_FUNCTION);
// Check that the modifications made to the GContext in the .pre function were reversed in .post
cl_assert(s_ctx.draw_state.compositing_mode ==
EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION);
cl_assert(gcolor_equal(s_ctx.draw_state.tint_color,
EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION));
// Note that additional checks are performed in the .pre and .post functions
}
typedef struct PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor {
GBitmapProcessor processor;
const GBitmap *expected_bitmap_in_post;
bool post_func_called;
} PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor;
static void post_function_called_even_if_pre_function_causes_nothing_to_be_drawn__post(
GBitmapProcessor *processor, GContext *ctx, const GBitmap *bitmap_used,
const GRect *global_clipped_grect_used) {
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *processor_with_data =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *)processor;
// Check that the rectangle here is empty to verify that nothing was drawn
cl_assert_equal_rect(*global_clipped_grect_used, GRectZero);
// Check that bitmap_used is what we expect it to be (the expected value is set in .pre)
cl_assert_equal_p(bitmap_used, processor_with_data->expected_bitmap_in_post);
// Record that the .post function was called
processor_with_data->post_func_called = true;
}
//! Helper function for testing that the .post function is called even if the .pre function
//! causes no bitmap to be drawn
static void prv_post_function_called_even_if_pre_function_causes_nothing_to_be_drawn_test(
GBitmapProcessorPreFunc pre_func) {
GBitmap bitmap = {0};
const GRect rect = EXPECTED_RECT_IN_PRE_FUNCTION;
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor processor =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor) {
.processor.pre = pre_func,
.processor.post = post_function_called_even_if_pre_function_causes_nothing_to_be_drawn__post,
};
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, &processor.processor);
// Check that the bitmap was not drawn
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 0);
// Check that the .post function was called even though the .pre function makes some change
// that causes no bitmap to be drawn
cl_assert(processor.post_func_called);
}
static void post_function_called_even_if_pre_function_specifies_null_bitmap__pre(
GBitmapProcessor *processor, GContext *ctx, const GBitmap **bitmap_to_use,
GRect *global_grect_to_use) {
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *processor_with_data =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *)processor;
// Change the bitmap to use to NULL to cause nothing to be drawn
*bitmap_to_use = NULL;
// We'll expect the bitmap we set there to be the bitmap passed into .post
processor_with_data->expected_bitmap_in_post = *bitmap_to_use;
}
void test_gbitmap_processor__post_function_called_even_if_pre_function_specifies_null_bitmap(void) {
prv_post_function_called_even_if_pre_function_causes_nothing_to_be_drawn_test(
post_function_called_even_if_pre_function_specifies_null_bitmap__pre);
};
static void post_function_called_even_if_pre_function_specifies_empty_rect__pre(
GBitmapProcessor *processor, GContext *ctx, const GBitmap **bitmap_to_use,
GRect *global_grect_to_use) {
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *processor_with_data =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *)processor;
// Change the rectangle to be empty to cause nothing to be drawn
*global_grect_to_use = GRectZero;
// We'll expect the bitmap we passed into graphics_draw_bitmap_in_rect_processed() in .post
// even though nothing will be drawn
processor_with_data->expected_bitmap_in_post = *bitmap_to_use;
}
void test_gbitmap_processor__post_function_called_even_if_pre_function_specifies_empty_rect(void) {
prv_post_function_called_even_if_pre_function_causes_nothing_to_be_drawn_test(
post_function_called_even_if_pre_function_specifies_empty_rect__pre);
};