mirror of
https://github.com/google/pebble
synced 2026-10-17 00:34:11 +02:00
392 lines
12 KiB
C
392 lines
12 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 "rocky_api_graphics_path2d.h"
|
|
#include "rocky_api_graphics.h"
|
|
#include "rocky_api_errors.h"
|
|
|
|
#include "applib/graphics/gpath.h"
|
|
#include "applib/graphics/graphics_circle.h"
|
|
#include "applib/graphics/graphics_line.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "rocky_api_util.h"
|
|
#include "rocky_api_util_args.h"
|
|
#include "system/passert.h"
|
|
#include "util/size.h"
|
|
#include "util/trig.h"
|
|
|
|
#define PATH2D_ARC "arc"
|
|
#define PATH2D_RECT "rect"
|
|
#define PATH2D_BEGINPATH "beginPath"
|
|
#define PATH2D_MOVETO "moveTo"
|
|
#define PATH2D_LINETO "lineTo"
|
|
#define PATH2D_CLOSEPATH "closePath"
|
|
#define ROCKY_CONTEXT2D_STROKE "stroke"
|
|
#define ROCKY_CONTEXT2D_FILL "fill"
|
|
|
|
#define MINIMUM_ARRAY_LEN (8)
|
|
|
|
// TODO: PBL-35780 make this part of app_state_get_rocky_runtime_context()
|
|
SECTION(".rocky_bss") T_STATIC RockyAPIPathStep *s_rocky_path_steps;
|
|
SECTION(".rocky_bss") T_STATIC size_t s_rocky_path_steps_array_len;
|
|
SECTION(".rocky_bss") T_STATIC size_t s_rocky_path_steps_num;
|
|
|
|
void rocky_api_graphics_path2d_reset_state(void) {
|
|
s_rocky_path_steps_num = 0;
|
|
|
|
task_free(s_rocky_path_steps);
|
|
s_rocky_path_steps = NULL;
|
|
s_rocky_path_steps_array_len = 0;
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_begin_path) {
|
|
rocky_api_graphics_path2d_reset_state();
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
static size_t prv_get_realloc_array_len(const size_t required_array_len) {
|
|
size_t len = s_rocky_path_steps_array_len ?: MINIMUM_ARRAY_LEN;
|
|
while (required_array_len > len) {
|
|
len *= 2;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static jerry_value_t prv_try_allocate_steps(const size_t num_steps_increment) {
|
|
const size_t required_array_len = (s_rocky_path_steps_num + num_steps_increment);
|
|
if (required_array_len <= s_rocky_path_steps_array_len) {
|
|
goto success;
|
|
}
|
|
const size_t new_array_len = prv_get_realloc_array_len(required_array_len);
|
|
void *new_steps_array = task_realloc(s_rocky_path_steps,
|
|
sizeof(RockyAPIPathStep) * new_array_len);
|
|
if (!new_steps_array) {
|
|
return rocky_error_oom("can't create more path steps");
|
|
}
|
|
s_rocky_path_steps = new_steps_array;
|
|
s_rocky_path_steps_array_len = new_array_len;
|
|
success:
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
#define TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(num_steps_increment) \
|
|
ROCKY_RETURN_IF_ERROR(prv_try_allocate_steps(num_steps_increment))
|
|
|
|
static jerry_value_t prv_add_pt(jerry_length_t argc, const jerry_value_t *argv,
|
|
RockyAPIPathStepType step_type) {
|
|
const double raw_x = argc > 0 ? (jerry_get_number_value(argv[0]) - 0.5) * FIXED_S16_3_FACTOR : 0;
|
|
const double raw_y = argc > 1 ? (jerry_get_number_value(argv[1]) - 0.5) * FIXED_S16_3_FACTOR : 0;
|
|
|
|
if (raw_x < INT16_MIN || raw_x > INT16_MAX || raw_y < INT16_MIN || raw_x > INT16_MAX) {
|
|
return rocky_error_argument_invalid("Value out of bounds");
|
|
}
|
|
|
|
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(1);
|
|
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = step_type,
|
|
.pt.xy = GPointPrecise((int16_t)raw_x, (int16_t)raw_y),
|
|
};
|
|
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_move_to) {
|
|
return prv_add_pt(argc, argv, RockyAPIPathStepType_MoveTo);
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_line_to) {
|
|
return prv_add_pt(argc, argv, RockyAPIPathStepType_LineTo);
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_stroke) {
|
|
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
|
|
|
GPoint p = {0};
|
|
GPointPrecise pp = {.x = {0}, .y = {0}};
|
|
bool moved_already = false;
|
|
|
|
#define ASSIGN_P(new_p) do { \
|
|
moved_already = true; \
|
|
p = GPointFromGPointPrecise(new_p); \
|
|
pp = new_p; \
|
|
} while (0)
|
|
|
|
for (size_t i = 0; i < s_rocky_path_steps_num; i++) {
|
|
RockyAPIPathStep *const step = &s_rocky_path_steps[i];
|
|
|
|
switch (step->type) {
|
|
case RockyAPIPathStepType_MoveTo: {
|
|
ASSIGN_P(step->pt.xy);
|
|
break;
|
|
}
|
|
case RockyAPIPathStepType_LineTo: {
|
|
if (moved_already) {
|
|
graphics_line_draw_precise_stroked(ctx, pp, step->pt.xy);
|
|
}
|
|
ASSIGN_P(step->pt.xy);
|
|
break;
|
|
}
|
|
case RockyAPIPathStepType_Arc: {
|
|
if (moved_already) {
|
|
const GPointPrecise pt_from = gpoint_from_polar_precise(
|
|
&step->arc.center, (uint16_t)step->arc.radius.raw_value, step->arc.angle_start);
|
|
graphics_line_draw_precise_stroked(ctx, pp, pt_from);
|
|
}
|
|
|
|
int32_t angle_start = step->arc.angle_start;
|
|
int32_t angle_end = step->arc.angle_end;
|
|
if (step->arc.anti_clockwise) {
|
|
const int32_t t = angle_start;
|
|
angle_start = angle_end;
|
|
angle_end = t;
|
|
}
|
|
while (angle_end < angle_start) {
|
|
angle_end += TRIG_MAX_ANGLE;
|
|
}
|
|
graphics_draw_arc_precise_internal(ctx, step->arc.center, step->arc.radius,
|
|
angle_start, angle_end);
|
|
|
|
const GPointPrecise pt_to = gpoint_from_polar_precise(
|
|
&step->arc.center, (uint16_t)step->arc.radius.raw_value, step->arc.angle_end);
|
|
ASSIGN_P(pt_to);
|
|
break;
|
|
}
|
|
default:
|
|
WTF;
|
|
}
|
|
}
|
|
#undef ASSIGN_P
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
static void prv_fill_points(GPoint *points, size_t num) {
|
|
if (num < 3) {
|
|
return;
|
|
}
|
|
|
|
GPath path = {
|
|
.num_points = num,
|
|
.points = points,
|
|
};
|
|
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
|
gpath_draw_filled(ctx, &path);
|
|
}
|
|
|
|
static GPointPrecise prv_point_add_vector_precise(GPointPrecise *pt, GVectorPrecise *v) {
|
|
return GPointPrecise(
|
|
pt->x.raw_value + v->dx.raw_value,
|
|
pt->y.raw_value + v->dy.raw_value);
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_fill) {
|
|
GPoint *const points = task_zalloc(sizeof(*points) * s_rocky_path_steps_num);
|
|
if (!points) {
|
|
return rocky_error_oom("too many points to fill");
|
|
}
|
|
size_t points_num = 0;
|
|
|
|
jerry_value_t rv;
|
|
|
|
#define ADD_P(pt) \
|
|
PBL_ASSERTN(points_num < s_rocky_path_steps_num); \
|
|
points[points_num++] = GPointFromGPointPrecise( \
|
|
prv_point_add_vector_precise(&(pt).xy, &(pt).fill_delta));
|
|
|
|
for (size_t i = 0; i < s_rocky_path_steps_num; i++) {
|
|
RockyAPIPathStep *const step = &s_rocky_path_steps[i];
|
|
switch (step->type) {
|
|
case RockyAPIPathStepType_MoveTo: {
|
|
prv_fill_points(points, points_num);
|
|
points_num = 0;
|
|
ADD_P(step->pt);
|
|
break;
|
|
}
|
|
case RockyAPIPathStepType_LineTo: {
|
|
ADD_P(step->pt);
|
|
break;
|
|
}
|
|
case RockyAPIPathStepType_Arc: {
|
|
rv = rocky_error_argument_invalid("fill() does not support arc()");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
rv = jerry_create_undefined();
|
|
|
|
prv_fill_points(points, points_num);
|
|
|
|
cleanup:
|
|
task_free(points);
|
|
return rv;
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_arc) {
|
|
GPointPrecise center;
|
|
Fixed_S16_3 radius;
|
|
double angle_1, angle_2;
|
|
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
|
ROCKY_ARG(center.x),
|
|
ROCKY_ARG(center.y),
|
|
ROCKY_ARG(radius),
|
|
ROCKY_ARG_ANGLE(angle_1),
|
|
ROCKY_ARG_ANGLE(angle_2));
|
|
|
|
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(1);
|
|
|
|
const bool anti_clockwise = (argc >= 6) ? jerry_get_boolean_value(argv[5]) : false;
|
|
|
|
// adjust for coordinate system
|
|
center.x.raw_value -= FIXED_S16_3_HALF.raw_value;
|
|
center.y.raw_value -= FIXED_S16_3_HALF.raw_value;
|
|
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_Arc,
|
|
.arc = (RockyAPIPathStepArc) {
|
|
.center = center,
|
|
.radius = radius,
|
|
// TODO: PBL-40555 consolidate angle handling here and in rocky_api_graphics.c
|
|
.angle_start = jerry_get_angle_value(argv[3]),
|
|
.angle_end = jerry_get_angle_value(argv[4]),
|
|
.anti_clockwise = anti_clockwise,
|
|
},
|
|
};
|
|
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_rect) {
|
|
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(5);
|
|
|
|
if (argc >= 4) {
|
|
GRectPrecise rect;
|
|
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(ROCKY_ARG(rect));
|
|
grect_precise_standardize(&rect);
|
|
|
|
// shift rectangle into coordinate system
|
|
const int16_t half_pt = FIXED_S16_3_HALF.raw_value;
|
|
rect.origin.x.raw_value -= half_pt;
|
|
rect.origin.y.raw_value -= half_pt;
|
|
|
|
// special casing for our filling algorithm to match fillRect()
|
|
const int16_t full_pt = FIXED_S16_3_ONE.raw_value;
|
|
const int16_t delta_t = full_pt;
|
|
const int16_t delta_r = full_pt;
|
|
const int16_t delta_b = 0;
|
|
const int16_t delta_l = 0;
|
|
const GVectorPrecise delta_tl = GVectorPrecise(delta_l, delta_t);
|
|
const GVectorPrecise delta_tr = GVectorPrecise(delta_r, delta_t);
|
|
const GVectorPrecise delta_br = GVectorPrecise(delta_r, delta_b);
|
|
const GVectorPrecise delta_bl = GVectorPrecise(delta_l, delta_b);
|
|
|
|
const Fixed_S16_3 right = grect_precise_get_max_x(&rect);
|
|
const Fixed_S16_3 bottom = grect_precise_get_max_y(&rect);
|
|
|
|
// top left
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_MoveTo,
|
|
.pt.xy = rect.origin,
|
|
.pt.fill_delta = delta_tl,
|
|
};
|
|
// top right
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_LineTo,
|
|
.pt.xy = GPointPrecise(right.raw_value, rect.origin.y.raw_value),
|
|
.pt.fill_delta = delta_tr,
|
|
};
|
|
// bottom right
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_LineTo,
|
|
.pt.xy = GPointPrecise(right.raw_value, bottom.raw_value),
|
|
.pt.fill_delta = delta_br,
|
|
};
|
|
// bottom left
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_LineTo,
|
|
.pt.xy = GPointPrecise(rect.origin.x.raw_value, bottom.raw_value),
|
|
.pt.fill_delta = delta_bl,
|
|
};
|
|
// top left again, to close path
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_LineTo,
|
|
.pt.xy = rect.origin,
|
|
.pt.fill_delta = delta_tl,
|
|
};
|
|
}
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_close_path) {
|
|
// lineTo() back to most-recent .moveTo()
|
|
|
|
if (s_rocky_path_steps_num < 2) {
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
RockyAPIPathStep *step = &s_rocky_path_steps[s_rocky_path_steps_num - 1];
|
|
|
|
// if the last step was a moveTo(), there's nothing to do
|
|
if (step->type == RockyAPIPathStepType_MoveTo) {
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(1);
|
|
|
|
do {
|
|
step--;
|
|
if (step->type == RockyAPIPathStepType_MoveTo) {
|
|
// add a lintTo() at the end
|
|
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
|
.type = RockyAPIPathStepType_LineTo,
|
|
.pt = step->pt,
|
|
};
|
|
break;
|
|
}
|
|
} while (step > &s_rocky_path_steps[0]);
|
|
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
void rocky_api_graphics_path2d_add_canvas_methods(jerry_value_t obj) {
|
|
rocky_add_function(obj, PATH2D_BEGINPATH, prv_begin_path);
|
|
rocky_add_function(obj, PATH2D_MOVETO, prv_move_to);
|
|
rocky_add_function(obj, PATH2D_LINETO, prv_line_to);
|
|
rocky_add_function(obj, PATH2D_ARC, prv_arc);
|
|
rocky_add_function(obj, PATH2D_RECT, prv_rect);
|
|
rocky_add_function(obj, PATH2D_CLOSEPATH, prv_close_path);
|
|
rocky_add_function(obj, ROCKY_CONTEXT2D_STROKE, prv_stroke);
|
|
rocky_add_function(obj, ROCKY_CONTEXT2D_FILL, prv_fill);
|
|
}
|
|
|
|
//! For unit testing
|
|
jerry_value_t rocky_api_graphics_path2d_try_allocate_steps(size_t inc_steps) {
|
|
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(inc_steps);
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
size_t rocky_api_graphics_path2d_min_array_len(void) {
|
|
return MINIMUM_ARRAY_LEN;
|
|
}
|
|
|
|
size_t rocky_api_graphics_path2d_array_len(void) {
|
|
return s_rocky_path_steps_array_len;
|
|
}
|
|
|
|
jerry_value_t rocky_api_graphics_path2d_call_fill(void) {
|
|
// Args aren't correct, but it doesn't matter right now because the function doesn't use them:
|
|
return prv_fill(jerry_create_undefined(), jerry_create_undefined(), NULL, 0);
|
|
}
|