mirror of
https://github.com/google/pebble.git
synced 2025-04-30 07:21:39 -04:00
417 lines
15 KiB
C
417 lines
15 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 "applib/graphics/graphics_circle.h"
|
|
#include "util/trig.h"
|
|
|
|
#include "clar.h"
|
|
#include "pebble_asserts.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
// stubs
|
|
#include "stubs_process_manager.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_app_state.h"
|
|
#include "stubs_heap.h"
|
|
|
|
GBitmap* graphics_capture_frame_buffer(GContext* ctx) {
|
|
return NULL;
|
|
}
|
|
bool graphics_release_frame_buffer(GContext* ctx, GBitmap* buffer) {
|
|
return true;
|
|
}
|
|
|
|
void graphics_draw_pixel(){}
|
|
void graphics_fill_rect(GContext* ctx, const GRect *rect) {}
|
|
void graphics_private_draw_horizontal_line(){}
|
|
void graphics_private_draw_vertical_line(){}
|
|
void graphics_private_plot_pixel(){}
|
|
void graphics_private_set_pixel(){}
|
|
|
|
/////////////////////////////
|
|
|
|
|
|
static GPointPrecise s_center;
|
|
static Fixed_S16_3 s_radius;
|
|
static Fixed_S16_3 s_radius_inner;
|
|
static Fixed_S16_3 s_radius_outer;
|
|
static int32_t s_angle_start;
|
|
static int32_t s_angle_end;
|
|
|
|
////////////////////////////
|
|
|
|
void test_graphics_circle__initialize(void) {
|
|
s_center = (GPointPrecise){};
|
|
s_radius = (Fixed_S16_3){};
|
|
s_radius_inner = (Fixed_S16_3){};
|
|
s_radius_outer = (Fixed_S16_3){};
|
|
}
|
|
|
|
void test_graphics_circle__gpoint_from_polar_returns_zero_for_null(void) {
|
|
const uint16_t radius = 5;
|
|
const GPoint result = gpoint_from_polar_internal(NULL, radius, 0);
|
|
cl_assert_equal_gpoint(result, GPointZero);
|
|
}
|
|
|
|
void test_graphics_circle__gpoint_from_polar_returns_correct_points(void) {
|
|
const uint16_t radius = 5;
|
|
GPoint result;
|
|
|
|
const GPoint origin_center = GPointZero;
|
|
// 90 degrees should be (5, 0)
|
|
result = gpoint_from_polar_internal(&origin_center, radius, (TRIG_MAX_ANGLE / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(5, 0));
|
|
// 270 (90 * 3) degrees should be (-5, 0)
|
|
result = gpoint_from_polar_internal(&origin_center, radius, (TRIG_MAX_ANGLE * 3 / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(-5, 0));
|
|
|
|
const GPoint offset_center = GPoint(1, 1);
|
|
// 90 degrees should be (6, 1)
|
|
result = gpoint_from_polar_internal(&offset_center, radius, (TRIG_MAX_ANGLE / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(6, 1));
|
|
// 270 (90 * 3) degrees should be (-4, 1)
|
|
result = gpoint_from_polar_internal(&offset_center, radius, (TRIG_MAX_ANGLE * 3 / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(-4, 1));
|
|
}
|
|
|
|
void test_graphics_circle__gpoint_from_polar_normalizes_input_angles(void) {
|
|
const uint16_t radius = 5;
|
|
const GPoint center = GPointZero;
|
|
|
|
GPoint result;
|
|
|
|
// -180 degrees should be (0, 5)
|
|
result = gpoint_from_polar_internal(¢er, radius, -(TRIG_MAX_ANGLE / 2));
|
|
cl_assert_equal_gpoint(result, GPoint(0, 5));
|
|
|
|
// -90 degrees should be (-5, 0)
|
|
result = gpoint_from_polar_internal(¢er, radius, -(TRIG_MAX_ANGLE / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(-5, 0));
|
|
|
|
// -450 degrees (-90 * 5 -> -90) should be (-5, 0)
|
|
result = gpoint_from_polar_internal(¢er, radius, -(TRIG_MAX_ANGLE * 5 / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(-5, 0));
|
|
|
|
// 450 degrees (90 * 5 -> 90) should be (0, 5)
|
|
result = gpoint_from_polar_internal(¢er, radius, (TRIG_MAX_ANGLE * 5 / 4));
|
|
cl_assert_equal_gpoint(result, GPoint(5, 0));
|
|
|
|
// 360 degrees (-> 0 degrees) should be (0, -5)
|
|
result = gpoint_from_polar_internal(¢er, radius, TRIG_MAX_ANGLE);
|
|
cl_assert_equal_gpoint(result, GPoint(0, -5));
|
|
|
|
// -360 degrees (-> 0 degrees) should be (0, -5)
|
|
result = gpoint_from_polar_internal(¢er, radius, -TRIG_MAX_ANGLE);
|
|
cl_assert_equal_gpoint(result, GPoint(0, -5));
|
|
}
|
|
|
|
void test_graphics_circle__gpoint_from_polar_correct_scale(void) {
|
|
// edge cases are covered above, this test only verifies that
|
|
// the internal implementation correctly scales
|
|
GPoint result = gpoint_from_polar(GRect(0, 0, 10, 10), GOvalScaleModeFillCircle, 0);
|
|
cl_assert_equal_gpoint(result, GPoint(4, 0));
|
|
}
|
|
|
|
void test_graphics_circle__grect_centered_from_polar(void) {
|
|
GOvalScaleMode mode = GOvalScaleModeFillCircle;
|
|
|
|
// Basic container rect with GPointZero origin
|
|
const GRect container_rect1 = GRect(0, 0, 10, 10);
|
|
const GRect resulting_rect1 = grect_centered_from_polar(container_rect1, mode, 0, GSize(3, 5));
|
|
cl_assert_equal_grect(resulting_rect1, GRect(3, -2, 3, 5));
|
|
|
|
// Container rect is offset from GPointZero
|
|
const GRect container_rect2 = GRect(2, 2, 4, 4);
|
|
const GRect resulting_rect2 = grect_centered_from_polar(container_rect2, mode, 0, GSize(2, 4));
|
|
cl_assert_equal_grect(resulting_rect2, GRect(3, 0, 2, 4));
|
|
|
|
// Odd-length width and height for container rect, 180 degree angle
|
|
const GRect container_rect3 = GRect(2, 2, 5, 5);
|
|
const GRect resulting_rect3 = grect_centered_from_polar(container_rect3, mode,
|
|
DEG_TO_TRIGANGLE(180), GSize(2, 4));
|
|
cl_assert_equal_grect(resulting_rect3, GRect(3, 4, 2, 4));
|
|
}
|
|
|
|
void test_graphics_circle__grect_centered_internal(void) {
|
|
GPointPrecise p1 = GPointPrecise(0, 0);
|
|
// GRectZero + standardize
|
|
cl_assert_equal_grect(GRect(0, 0, 0, 0), grect_centered_internal(&p1, GSize(0, 0)));
|
|
cl_assert_equal_grect(GRect(0, -1, 1, 2), grect_centered_internal(&p1, GSize(-1, -2)));
|
|
|
|
// handles fixed point
|
|
cl_assert_equal_grect(GRect(-1, -1, 2, 2), grect_centered_internal(&p1, GSize(2, 2)));
|
|
p1.x.raw_value += FIXED_S16_3_HALF.raw_value;
|
|
cl_assert_equal_grect(GRect(0, -1, 2, 2), grect_centered_internal(&p1, GSize(2, 2)));
|
|
|
|
// Repeat for an offset center point
|
|
GPointPrecise p2 = GPointPreciseFromGPoint(GPoint(5, 5));
|
|
|
|
// GRectZero + standardize
|
|
cl_assert_equal_grect(GRect(5, 5, 0, 0), grect_centered_internal(&p2, GSize(0, 0)));
|
|
cl_assert_equal_grect(GRect(5, 4, 1, 2), grect_centered_internal(&p2, GSize(-1, -2)));
|
|
|
|
// handles fixed point
|
|
cl_assert_equal_grect(GRect(4, 4, 2, 2), grect_centered_internal(&p2, GSize(2, 2)));
|
|
p2.x.raw_value += FIXED_S16_3_HALF.raw_value;
|
|
cl_assert_equal_grect(GRect(5, 4, 2, 2), grect_centered_internal(&p2, GSize(2, 2)));
|
|
|
|
// Repeat for a positive offset center point with 0.5 fractions
|
|
GPointPrecise p3 = GPointPreciseFromGPoint(GPoint(5, 5));
|
|
p3.x.raw_value += FIXED_S16_3_HALF.raw_value;
|
|
p3.y.raw_value += FIXED_S16_3_HALF.raw_value;
|
|
|
|
// GRectZero + standardize
|
|
cl_assert_equal_grect(GRect(6, 6, 0, 0), grect_centered_internal(&p3, GSize(0, 0)));
|
|
cl_assert_equal_grect(GRect(5, 5, 1, 2), grect_centered_internal(&p3, GSize(-1, -2)));
|
|
|
|
// handles fixed point
|
|
cl_assert_equal_grect(GRect(5, 5, 2, 2), grect_centered_internal(&p3, GSize(2, 2)));
|
|
p3.x.raw_value += FIXED_S16_3_HALF.raw_value;
|
|
cl_assert_equal_grect(GRect(5, 5, 2, 2), grect_centered_internal(&p3, GSize(2, 2)));
|
|
|
|
// Repeat for a negative offset center point with 0.5 fractions
|
|
GPointPrecise p4 = GPointPreciseFromGPoint(GPoint(-5, -5));
|
|
p4.x.raw_value -= FIXED_S16_3_HALF.raw_value;
|
|
p4.y.raw_value -= FIXED_S16_3_HALF.raw_value;
|
|
|
|
// GRectZero + standardize
|
|
cl_assert_equal_grect(GRect(-5, -5, 0, 0), grect_centered_internal(&p4, GSize(0, 0)));
|
|
cl_assert_equal_grect(GRect(-6, -6, 1, 2), grect_centered_internal(&p4, GSize(-1, -2)));
|
|
|
|
// handles fixed point
|
|
cl_assert_equal_grect(GRect(-6, -6, 2, 2), grect_centered_internal(&p4, GSize(2, 2)));
|
|
p4.x.raw_value += FIXED_S16_3_HALF.raw_value;
|
|
cl_assert_equal_grect(GRect(-6, -6, 2, 2), grect_centered_internal(&p4, GSize(2, 2)));
|
|
}
|
|
|
|
#define cl_assert_fixedS16_3(v, f) \
|
|
cl_assert_equal_i((v).raw_value, (int)((f) * FIXED_S16_3_ONE.raw_value))
|
|
|
|
#define cl_assert_gpoint_precise(p, px, py) \
|
|
do { \
|
|
cl_assert_fixedS16_3(p.x, px); \
|
|
cl_assert_fixedS16_3(p.y, py); \
|
|
} while(0)
|
|
|
|
|
|
void test_graphics_circle__grect_polar_calc_values_handles_null(void) {
|
|
GPointPrecise center = {};
|
|
Fixed_S16_3 radius = {};
|
|
const GRect r = GRect(0, 0, 3, 5);
|
|
const GOvalScaleMode mode = GOvalScaleModeFitCircle;
|
|
|
|
grect_polar_calc_values(&r, mode, NULL, NULL);
|
|
grect_polar_calc_values(&r, mode, ¢er, NULL);
|
|
grect_polar_calc_values(&r, mode, NULL, &radius);
|
|
|
|
cl_assert_gpoint_precise(center, 1, 2);
|
|
cl_assert_fixedS16_3(radius, 1);
|
|
|
|
grect_polar_calc_values(NULL, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 1, 2);
|
|
cl_assert_fixedS16_3(radius, 1);
|
|
}
|
|
|
|
void test_graphics_circle__grect_polar_calc_values_edge_cases(void) {
|
|
GPointPrecise center = {};
|
|
Fixed_S16_3 radius = {};
|
|
const GOvalScaleMode mode = GOvalScaleModeFillCircle;
|
|
|
|
GRect r = GRect(0, 5, 0, 0);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 0, 5);
|
|
cl_assert_fixedS16_3(radius, 0);
|
|
|
|
// 1 pixel width means radius of 0
|
|
r = GRect(-1, -5, 1, 1);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, -1, -5);
|
|
cl_assert_fixedS16_3(radius, 0);
|
|
|
|
// 2 pixel width means: center is 1px from side, 0.5 pixels to center of outer pixels
|
|
r = GRect(-1, -5, 2, 2);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, -0.5, -4.5);
|
|
cl_assert_fixedS16_3(radius, 0.5);
|
|
}
|
|
|
|
void test_graphics_circle__grect_polar_calc_values_standardizes(void) {
|
|
GPointPrecise center = {};
|
|
Fixed_S16_3 radius = {};
|
|
const GOvalScaleMode mode = GOvalScaleModeFitCircle;
|
|
|
|
GRect r = GRect(0, 0, 10, 20);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 4.5, 9.5);
|
|
cl_assert_fixedS16_3(radius, 4.5);
|
|
|
|
r = GRect(0, 0, -10, -20);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, -5.5, -10.5);
|
|
cl_assert_fixedS16_3(radius, 4.5);
|
|
}
|
|
|
|
void test_graphics_circle__grect_polar_calc_values_square(void) {
|
|
GPointPrecise center = {};
|
|
Fixed_S16_3 radius = {};
|
|
GRect r = GRect(0, 0, 5, 5);
|
|
GOvalScaleMode mode = GOvalScaleModeFitCircle; // irrelevant as we deal with squares
|
|
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 2, 2);
|
|
cl_assert_fixedS16_3(radius, 2);
|
|
|
|
r = GRect(0, 0, 6, 6);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 2.5, 2.5);
|
|
cl_assert_fixedS16_3(radius, 2.5);
|
|
|
|
r = GRect(0, 0, 10, 10);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 4.5, 4.5);
|
|
cl_assert_fixedS16_3(radius, 4.5);
|
|
|
|
r = GRect(1, 1, 9, 9);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 5, 5);
|
|
cl_assert_fixedS16_3(radius, 4);
|
|
|
|
r = GRect(2, 2, 8, 8);
|
|
grect_polar_calc_values(&r, mode, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 5.5, 5.5);
|
|
cl_assert_fixedS16_3(radius, 3.5);
|
|
}
|
|
|
|
void test_graphics_circle__grect_polar_calc_values_mode(void) {
|
|
GPointPrecise center = {};
|
|
Fixed_S16_3 radius = {};
|
|
GRect r = GRect(0, 0, 144, 168);
|
|
|
|
grect_polar_calc_values(&r, GOvalScaleModeFitCircle, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 144 / 2 - 0.5, 168 / 2 - 0.5);
|
|
cl_assert_fixedS16_3(radius, 144 / 2 - 0.5);
|
|
|
|
grect_polar_calc_values(&r, GOvalScaleModeFillCircle, ¢er, &radius);
|
|
cl_assert_gpoint_precise(center, 144 / 2 - 0.5, 168 / 2 - 0.5);
|
|
cl_assert_fixedS16_3(radius, 168 / 2 - 0.5);
|
|
}
|
|
|
|
void graphics_draw_arc_precise_internal(GContext *ctx, GPointPrecise center,
|
|
Fixed_S16_3 radius,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
s_center = center;
|
|
s_radius = radius;
|
|
}
|
|
|
|
void test_graphics_circle__draw_arc(void) {
|
|
cl_assert_gpoint_precise(s_center, 0, 0);
|
|
cl_assert_fixedS16_3(s_radius, 0);
|
|
|
|
graphics_draw_arc(NULL, GRect(0, 0, 10, 12), GOvalScaleModeFitCircle, 0, 0);
|
|
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
|
|
cl_assert_fixedS16_3(s_radius, 4.5);
|
|
}
|
|
|
|
void test_graphics_circle__fill_oval(void) {
|
|
cl_assert_gpoint_precise(s_center, 0, 0);
|
|
cl_assert_fixedS16_3(s_radius_inner, 0);
|
|
cl_assert_fixedS16_3(s_radius_outer, 0);
|
|
cl_assert_equal_i(s_angle_start, 0);
|
|
cl_assert_equal_i(s_angle_end, 0);
|
|
|
|
graphics_fill_oval(NULL, GRect(0, 0, 10, 12), GOvalScaleModeFitCircle);
|
|
|
|
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
|
|
cl_assert_fixedS16_3(s_radius_outer, 4.5);
|
|
cl_assert(s_radius_inner.integer <= 0);
|
|
cl_assert_equal_i(s_angle_start, 0);
|
|
cl_assert_equal_i(s_angle_end, TRIG_MAX_ANGLE);
|
|
|
|
graphics_fill_oval(NULL, GRect(10, 12, -10, -12), GOvalScaleModeFitCircle);
|
|
|
|
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
|
|
cl_assert_fixedS16_3(s_radius_outer, 4.5);
|
|
cl_assert(s_radius_inner.integer <= 0);
|
|
cl_assert_equal_i(s_angle_start, 0);
|
|
cl_assert_equal_i(s_angle_end, TRIG_MAX_ANGLE);
|
|
|
|
graphics_fill_oval(NULL, GRect(0, 0, 0, 0), GOvalScaleModeFillCircle);
|
|
cl_assert_gpoint_precise(s_center, 0.0, 0.0);
|
|
cl_assert_fixedS16_3(s_radius_outer, 0.0);
|
|
cl_assert(s_radius_inner.integer <= 0);
|
|
cl_assert_equal_i(s_angle_start, 0);
|
|
cl_assert_equal_i(s_angle_end, TRIG_MAX_ANGLE);
|
|
}
|
|
|
|
void graphics_fill_radial_precise_internal(GContext *ctx, GPointPrecise center,
|
|
Fixed_S16_3 radius_inner, Fixed_S16_3 radius_outer,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
s_center = center;
|
|
s_radius_inner = radius_inner;
|
|
s_radius_outer = radius_outer;
|
|
s_angle_start = angle_start;
|
|
s_angle_end = angle_end;
|
|
}
|
|
|
|
|
|
void test_graphics_circle__fill_radial(void) {
|
|
GContext *ctx = (GContext *)&ctx;
|
|
cl_assert_gpoint_precise(s_center, 0, 0);
|
|
cl_assert_fixedS16_3(s_radius_inner, 0);
|
|
cl_assert_fixedS16_3(s_radius_outer, 0);
|
|
|
|
graphics_fill_radial(NULL, GRect(0, 0, 10, 12), GOvalScaleModeFitCircle, 3, 0, 0);
|
|
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
|
|
cl_assert_fixedS16_3(s_radius_outer, 4.5);
|
|
cl_assert_fixedS16_3(s_radius_inner, 1.5);
|
|
}
|
|
|
|
void test_graphics_circle__DEG_TO_TRIGANGLE(void) {
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(720), TRIG_MAX_ANGLE * 2);
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(-720), -TRIG_MAX_ANGLE * 2);
|
|
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(360), TRIG_MAX_ANGLE);
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(-360), -TRIG_MAX_ANGLE);
|
|
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(180), TRIG_PI);
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(-180), -TRIG_PI);
|
|
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(90), TRIG_PI / 2);
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(-90), -TRIG_PI / 2);
|
|
|
|
cl_assert_equal_i(DEG_TO_TRIGANGLE(0), 0);
|
|
}
|
|
|
|
void test_graphics_circle__TRIGANGLE_TO_DEG(void) {
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE * 2), 720);
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_MAX_ANGLE * 2), -720);
|
|
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE), 360);
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_MAX_ANGLE), -360);
|
|
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_PI / 2), 90);
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_PI / 2), -90);
|
|
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE / 2), 180);
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_MAX_ANGLE / 2), -180);
|
|
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_PI), 180);
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_PI), -180);
|
|
|
|
cl_assert_equal_i(TRIGANGLE_TO_DEG(0), 0);
|
|
}
|