mirror of
https://github.com/google/pebble.git
synced 2025-04-30 07:21:39 -04:00
1489 lines
58 KiB
C
1489 lines
58 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 "graphics_circle.h"
|
|
#include "graphics_circle_private.h"
|
|
#include "graphics.h"
|
|
#include "graphics_private.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "system/passert.h"
|
|
#include "util/math.h"
|
|
#include "util/size.h"
|
|
#include "util/trig.h"
|
|
|
|
static Fixed_S16_3 prv_get_circle_border(int16_t y, uint16_t radius) {
|
|
// Match to the precision we need here
|
|
y *= FIXED_S16_3_ONE.raw_value;
|
|
radius *= FIXED_S16_3_ONE.raw_value;
|
|
|
|
return (Fixed_S16_3){.raw_value = radius - integer_sqrt(radius * radius - y * y)};
|
|
}
|
|
|
|
static Fixed_S16_3 prv_get_ellipsis_border(Fixed_S16_3 offset, uint32_t offset_radius_sq,
|
|
uint32_t opposite_radius_sq) {
|
|
if (offset_radius_sq == opposite_radius_sq) {
|
|
// We're dealing with a circle
|
|
return (Fixed_S16_3){.raw_value = integer_sqrt((offset_radius_sq << FIXED_S16_3_PRECISION) -
|
|
offset.raw_value * offset.raw_value)};
|
|
}
|
|
|
|
return (Fixed_S16_3){
|
|
.raw_value = (integer_sqrt((opposite_radius_sq - opposite_radius_sq *
|
|
((offset.raw_value * offset.raw_value) >> FIXED_S16_3_PRECISION) /
|
|
offset_radius_sq) << FIXED_S16_3_PRECISION))
|
|
};
|
|
}
|
|
|
|
static GPointPrecise prv_get_rotated_precise_point(GPointPrecise center, uint16_t radius,
|
|
uint32_t angle) {
|
|
return GPointPrecise((center.x.raw_value + radius * sin_lookup(angle) / TRIG_MAX_RATIO),
|
|
(center.y.raw_value - radius * cos_lookup(angle) / TRIG_MAX_RATIO));
|
|
}
|
|
|
|
static GPointPrecise prv_get_rotated_precise_point_for_ellipsis(GPointPrecise center,
|
|
uint16_t radius_x,
|
|
uint16_t radius_y,
|
|
uint32_t angle) {
|
|
// PBL-25637: goes bonkers for ellipsis and angles around 90° and 270°
|
|
if (radius_x == radius_y) {
|
|
// We're dealing with circle here - theres an easier way...
|
|
return prv_get_rotated_precise_point(center, radius_x, angle);
|
|
}
|
|
|
|
// This is an edge case due to fixedpoint math
|
|
if (angle % QUADRANT_ANGLE == 0) {
|
|
radius_x <<= FIXED_S16_3_PRECISION;
|
|
radius_y <<= FIXED_S16_3_PRECISION;
|
|
|
|
switch (angle / QUADRANT_ANGLE) {
|
|
case 0:
|
|
return GPointPrecise(center.x.raw_value, center.y.raw_value - radius_y);
|
|
case 1:
|
|
return GPointPrecise(center.x.raw_value + radius_x, center.y.raw_value);
|
|
case 2:
|
|
return GPointPrecise(center.x.raw_value, center.y.raw_value + radius_y);
|
|
default:
|
|
return GPointPrecise(center.x.raw_value - radius_x, center.y.raw_value);
|
|
}
|
|
}
|
|
|
|
// This algorithm operates on angle starting at our 90° mark, so we add 90°
|
|
// and flip x/y coordinates (see last line of this function)
|
|
angle = (angle + (TRIG_MAX_ANGLE / 4)) % TRIG_MAX_ANGLE;
|
|
|
|
// This is going to be divided by fixed-point precision number so division here is unnecessary
|
|
int32_t radius_xy = ((int32_t)radius_x * radius_y);
|
|
int32_t radius_xx = ((int32_t)radius_x * radius_x) >> FIXED_S16_3_PRECISION;
|
|
int32_t radius_yy = ((int32_t)radius_y * radius_y) >> FIXED_S16_3_PRECISION;
|
|
|
|
int64_t sin = sin_lookup(angle);
|
|
int64_t cos = cos_lookup(angle);
|
|
|
|
int64_t sin_sq = sin * sin / TRIG_MAX_RATIO;
|
|
int64_t cos_sq = cos * cos / TRIG_MAX_RATIO;
|
|
|
|
// We simulate tan(angle) by sin(angle)/cos(angle)
|
|
int64_t rx_tan = radius_xx * sin_sq;
|
|
if (cos_sq != 0) {
|
|
rx_tan /= cos_sq;
|
|
}
|
|
|
|
int32_t sqrt_x = integer_sqrt((radius_yy + rx_tan) << FIXED_S16_3_PRECISION);
|
|
|
|
int16_t x = 0;
|
|
if (sqrt_x > 0) {
|
|
x = radius_xy / sqrt_x;
|
|
}
|
|
|
|
// Between 90° and 270° we flip the x
|
|
if (angle >= (TRIG_MAX_ANGLE / 4) && angle < (TRIG_MAX_ANGLE * 3 / 4)) {
|
|
x *= -1;
|
|
}
|
|
|
|
// And y in this case is just x multiplied by tan(angle)
|
|
int16_t y = x * sin / cos;
|
|
|
|
// Flipping results by center point
|
|
return GPointPrecise(center.x.raw_value - x, center.y.raw_value - y);
|
|
}
|
|
|
|
T_STATIC void graphics_circle_quadrant_draw_1px_non_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
GCornerMask quadrant) {
|
|
int f = 1 - radius;
|
|
int ddF_x = 1;
|
|
int ddF_y = -2 * radius;
|
|
int x = 0;
|
|
uint16_t y = radius;
|
|
|
|
p.x += ctx->draw_state.drawing_box.origin.x;
|
|
p.y += ctx->draw_state.drawing_box.origin.y;
|
|
|
|
while (x < y) {
|
|
if (f >= 0) {
|
|
y--;
|
|
ddF_y += 2;
|
|
f += ddF_y;
|
|
}
|
|
|
|
x++;
|
|
ddF_x += 2;
|
|
f += ddF_x;
|
|
|
|
if (quadrant & GCornerBottomRight) {
|
|
graphics_private_set_pixel(ctx, GPoint(p.x + x, p.y + y));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x + y, p.y + x));
|
|
}
|
|
|
|
if (quadrant & GCornerTopRight) {
|
|
graphics_private_set_pixel(ctx, GPoint(p.x + x, p.y - y));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x + y, p.y - x));
|
|
}
|
|
|
|
if (quadrant & GCornerBottomLeft) {
|
|
graphics_private_set_pixel(ctx, GPoint(p.x - x, p.y + y));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x - y, p.y + x));
|
|
}
|
|
|
|
if (quadrant & GCornerTopLeft) {
|
|
graphics_private_set_pixel(ctx, GPoint(p.x - x, p.y - y));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x - y, p.y - x));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void prv_plot4(GBitmap *fb, GRect *clip_box, GPoint center, GPoint offset, int8_t brightness,
|
|
GColor stroke_color, GCornerMask quadrant) {
|
|
/*
|
|
* This will mirror given offset point over x and y coordinates of given center point
|
|
* |
|
|
* x1 | x
|
|
* |
|
|
* |
|
|
* -------+--------
|
|
* |
|
|
* |
|
|
* x2 | x3
|
|
* |
|
|
*
|
|
* + center point
|
|
* - x coordinate mirror line
|
|
* | y coordinate mirror line
|
|
* x given offset point
|
|
* xn mirrored points
|
|
*/
|
|
|
|
for (unsigned int i=0; i < ARRAY_LENGTH(quadrant_mask_mul); i++) {
|
|
if (quadrant_mask_mul[i].mask & quadrant) {
|
|
int16_t x = center.x + (offset.x * quadrant_mask_mul[i].x_mul);
|
|
int16_t y = center.y + (offset.y * quadrant_mask_mul[i].y_mul);
|
|
graphics_private_plot_pixel(fb, clip_box, x, y, brightness, stroke_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void prv_plot8(GBitmap *fb, GRect *clip_box, GPoint center, GPoint offset, int8_t brightness,
|
|
GColor stroke_color, GCornerMask quadrant) {
|
|
/*
|
|
* This will mirror given offset point over all eighths of the circle at given center
|
|
* \ x8| x /
|
|
* \ | /
|
|
* \ | /
|
|
* x7 \ | / x2
|
|
* -------+--------
|
|
* x6 / | \ x3
|
|
* / | \
|
|
* / | \
|
|
* / x5| x4 \
|
|
*
|
|
* + center point
|
|
* - x coordinate mirror line
|
|
* | y coordinate mirror line
|
|
* / 45 degree mirror line
|
|
* \ 135 degree mirror line
|
|
* x given offset point
|
|
* xn mirrored points
|
|
*/
|
|
|
|
prv_plot4(fb, clip_box, center, offset, brightness, stroke_color, quadrant);
|
|
|
|
// Swapping x and y for rest of the circle
|
|
prv_plot4(fb, clip_box, center, GPoint(offset.y, offset.x), brightness, stroke_color, quadrant);
|
|
}
|
|
|
|
#if PBL_COLOR
|
|
T_STATIC void graphics_circle_quadrant_draw_1px_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
GCornerMask quadrant) {
|
|
/* This will draw antialiased circle with width of 1px, can be drawn in quadrants
|
|
* Based on wu-xiang line drawing, will draw circle in two steps
|
|
* 1. Calculate point on the edge of eighth of the circle and plot it around by mirroring
|
|
* - if point is matching pixel perfectly thats going to be on fully colored pixel
|
|
* - if theres fraction, two pixels will be colored accordingly
|
|
* 2. Fill special case pixels (pixels that are between mirrored eighths)
|
|
* - special code to avoid overdrawing of those pixels and preserve antialiasing
|
|
* - three pixels calculated for circle with radius > 6
|
|
* - two pixels calculated for circle with radius < 6
|
|
*
|
|
* Theres also special case for the radius of 3, where algorithm couldnt stop at right
|
|
* point and wasn't drawing two pixels on each quadrant
|
|
*
|
|
* Here's quadrant example:
|
|
*
|
|
* 45 degree angle (radius * (sqrt(2)/2))
|
|
* |
|
|
* v
|
|
*
|
|
* | | | |
|
|
* | | |
|
|
* | o
|
|
* o o <- 45 degree angle (radius * (sqrt(2)/2))
|
|
* - -
|
|
* - -
|
|
* - -
|
|
* -
|
|
* x -
|
|
*
|
|
* | original calculated pixels for plotting
|
|
* - mirrored eight of the circle (will mirror more of them if necessary)
|
|
* o special case pixels
|
|
* x center of the circle
|
|
*/
|
|
|
|
// To match whats being drawn by non-aa graphics_draw_circle...
|
|
radius++;
|
|
|
|
// Apply drawing_box
|
|
p.x += ctx->draw_state.drawing_box.origin.x;
|
|
p.y += ctx->draw_state.drawing_box.origin.y;
|
|
|
|
// As close to sqrt(2)/2 as possible
|
|
int stop_progress = radius * 707 / 1000;
|
|
int progress;
|
|
|
|
uint16_t radius_fixed = radius * FIXED_S16_3_ONE.raw_value;
|
|
int16_t weighting_compliment_mask = MAX_PLOT_BRIGHTNESS;
|
|
int16_t weighting = 0;
|
|
|
|
// Locking framebuffer...
|
|
GBitmap *framebuffer = graphics_capture_frame_buffer(ctx);
|
|
GColor stroke_color = ctx->draw_state.stroke_color;
|
|
|
|
// Step 1
|
|
for (progress = 0; progress < stop_progress; progress++) {
|
|
Fixed_S16_3 edge = (Fixed_S16_3){.raw_value = radius_fixed -
|
|
prv_get_circle_border(progress, radius).raw_value};
|
|
|
|
if (edge.integer != 0) {
|
|
weighting = (edge.fraction >> 1);
|
|
|
|
prv_plot8(framebuffer, &ctx->draw_state.clip_box, p, GPoint(edge.integer - 1, progress),
|
|
weighting, stroke_color, quadrant);
|
|
|
|
prv_plot8(framebuffer, &ctx->draw_state.clip_box, p, GPoint(edge.integer, progress),
|
|
weighting ^ weighting_compliment_mask, stroke_color, quadrant);
|
|
} else {
|
|
prv_plot8(framebuffer, &ctx->draw_state.clip_box, p, GPoint(edge.integer, progress),
|
|
MAX_PLOT_OPACITY, stroke_color, quadrant);
|
|
}
|
|
}
|
|
|
|
// Behold for magic number 3!!!
|
|
// Note: magic numbers explained in main comment for this function
|
|
int special_case_pixels = 3;
|
|
|
|
// Accompanied by magic number 7 (not 6, we increased radius at beginning of this function)
|
|
if (radius < 7) {
|
|
// And sometimes magic number 2
|
|
special_case_pixels = 2;
|
|
}
|
|
|
|
// Step 2
|
|
// Special code for filling gap between mirrored parts in a manner that wont overdraw pixels
|
|
for (; progress < stop_progress + special_case_pixels; progress++) {
|
|
Fixed_S16_3 edge = (Fixed_S16_3){.raw_value = radius_fixed -
|
|
prv_get_circle_border(progress, radius).raw_value};
|
|
|
|
if (edge.integer != 0) {
|
|
weighting = (edge.fraction >> 1);
|
|
|
|
if (edge.integer - 1 > radius - stop_progress) {
|
|
prv_plot4(framebuffer, &ctx->draw_state.clip_box, p, GPoint(edge.integer - 1, progress),
|
|
weighting, stroke_color, quadrant);
|
|
}
|
|
|
|
if (edge.integer > radius - stop_progress) {
|
|
prv_plot4(framebuffer, &ctx->draw_state.clip_box, p, GPoint(edge.integer, progress),
|
|
weighting ^ weighting_compliment_mask, stroke_color, quadrant);
|
|
}
|
|
} else {
|
|
if (edge.integer > radius - stop_progress) {
|
|
prv_plot4(framebuffer, &ctx->draw_state.clip_box, p, GPoint(edge.integer, progress),
|
|
MAX_PLOT_OPACITY, stroke_color, quadrant);
|
|
}
|
|
}
|
|
}
|
|
|
|
// And for grand finale, super special case for radius of 4 (3 outside of this funct):
|
|
if (radius == 4) {
|
|
prv_plot4(framebuffer, &ctx->draw_state.clip_box, p, GPoint(2, 2), MAX_PLOT_OPACITY,
|
|
stroke_color, quadrant);
|
|
|
|
prv_plot4(framebuffer, &ctx->draw_state.clip_box, p, GPoint(2, 3), 2, stroke_color, quadrant);
|
|
}
|
|
|
|
// Releasing framebuffer...
|
|
graphics_release_frame_buffer(ctx, framebuffer);
|
|
}
|
|
#endif // PBL_COLOR
|
|
|
|
static void prv_circle_arc_draw_1px(GContext* ctx, GPoint center, uint16_t radius,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
// TODO: PBL-23119 Write non-aa function
|
|
// TODO: PBL-24777 Write new 1px functions (with support for ellipsis)
|
|
// prv_circle_quadrant_part_1px_aa(ctx, center, radius, config.start_quadrant.angle_start,
|
|
// config.start_quadrant.angle_end);
|
|
//
|
|
// prv_circle_quadrant_part_1px_aa(ctx, center, radius, config.end_quadrant.angle_start,
|
|
// config.end_quadrant.angle_end);
|
|
//
|
|
// if (config.full_quadrants != GCornerNone) {
|
|
// graphics_circle_quadrant_draw_1px_aa(ctx, center, radius, config.full_quadrants);
|
|
// }
|
|
}
|
|
|
|
inline void prv_vline_quadrant(GCornerMask quadrant, GCornerMask desired, GContext *ctx, int16_t x,
|
|
Fixed_S16_3 start, Fixed_S16_3 end) {
|
|
if (quadrant & desired) {
|
|
graphics_private_draw_vertical_line(ctx, x, start, end);
|
|
}
|
|
}
|
|
|
|
inline void prv_hline_quadrant(GCornerMask quadrant, GCornerMask desired, GContext *ctx, int16_t y,
|
|
Fixed_S16_3 start, Fixed_S16_3 end) {
|
|
if (quadrant & desired) {
|
|
graphics_private_draw_horizontal_line(ctx, y, start, end);
|
|
}
|
|
}
|
|
|
|
static void prv_stroke_circle_quadrant_full(GContext* ctx, GPoint p, uint16_t radius,
|
|
uint8_t stroke_width, GCornerMask quadrant) {
|
|
// This algorithm will draw stroked circle with variable width (only odd numbers for now)
|
|
const uint8_t half_stroke_width = stroke_width / 2;
|
|
const int16_t inner_radius = radius - half_stroke_width;
|
|
const uint8_t outer_radius = radius + half_stroke_width;
|
|
|
|
if (inner_radius < 1) {
|
|
// Hack for filling circles: filling is done by line primitives using stroke_color by default
|
|
GColor temp_color = ctx->draw_state.fill_color;
|
|
ctx->draw_state.fill_color = ctx->draw_state.stroke_color;
|
|
|
|
#if PBL_COLOR
|
|
if (ctx->draw_state.antialiased) {
|
|
graphics_internal_circle_quadrant_fill_aa(ctx, p, outer_radius, quadrant);
|
|
} else {
|
|
graphics_circle_quadrant_fill_non_aa(ctx, p, outer_radius, quadrant);
|
|
}
|
|
#else
|
|
graphics_circle_quadrant_fill_non_aa(ctx, p, outer_radius, quadrant);
|
|
#endif
|
|
|
|
// Restore original status
|
|
ctx->draw_state.fill_color = temp_color;
|
|
return;
|
|
}
|
|
|
|
// Since fill_oval will use fill color, we have to swap those:
|
|
const GColor fill_color = ctx->draw_state.fill_color;
|
|
ctx->draw_state.fill_color = ctx->draw_state.stroke_color;
|
|
|
|
// For pixel matching we need to decrease inner radius...
|
|
prv_fill_oval_quadrant(ctx, p, outer_radius, outer_radius,
|
|
inner_radius - 1, inner_radius - 1, quadrant);
|
|
|
|
ctx->draw_state.fill_color = fill_color;
|
|
}
|
|
|
|
static void prv_stroke_circle_quadrant_full_override_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
uint8_t stroke_width, GCornerMask quadrant,
|
|
bool anti_aliased) {
|
|
#if PBL_COLOR
|
|
// Force antialiasing setting
|
|
bool temp_anti_aliased = ctx->draw_state.antialiased;
|
|
ctx->draw_state.antialiased = anti_aliased;
|
|
#endif
|
|
|
|
// Call stroke circle quadrant function
|
|
prv_stroke_circle_quadrant_full(ctx, p, radius, stroke_width, quadrant);
|
|
|
|
#if PBL_COLOR
|
|
// Restore previous antialiasing setting
|
|
ctx->draw_state.antialiased = temp_anti_aliased;
|
|
#endif
|
|
}
|
|
|
|
#if PBL_COLOR
|
|
//! Draws anit-aliased stroked quadrant of a circle
|
|
//! @internal
|
|
T_STATIC void graphics_circle_quadrant_draw_stroked_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
uint8_t stroke_width, GCornerMask quadrant) {
|
|
prv_stroke_circle_quadrant_full_override_aa(ctx, p, radius, stroke_width, quadrant, true);
|
|
}
|
|
#endif // PBL_COLOR
|
|
|
|
//! Draws aliased stroked quadrant of a circle
|
|
//! @internal
|
|
T_STATIC void graphics_circle_quadrant_draw_stroked_non_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
uint8_t stroke_width,
|
|
GCornerMask quadrant) {
|
|
prv_stroke_circle_quadrant_full_override_aa(ctx, p, radius, stroke_width, quadrant, false);
|
|
}
|
|
|
|
void graphics_circle_quadrant_draw(GContext* ctx, GPoint p, uint16_t radius, GCornerMask quadrant) {
|
|
uint8_t stroke_width = ctx->draw_state.stroke_width;
|
|
#if PBL_COLOR
|
|
if (ctx->draw_state.antialiased) {
|
|
if (stroke_width > 1) {
|
|
// Antialiased and Stroke Width > 1
|
|
graphics_circle_quadrant_draw_stroked_aa(ctx, p, radius, stroke_width, quadrant);
|
|
return;
|
|
} else {
|
|
// Antialiased and Stroke Width == 1 (not supported on 1-bit color)
|
|
graphics_circle_quadrant_draw_1px_aa(ctx, p, radius, quadrant);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
if (stroke_width > 1) {
|
|
// Non-Antialiased and Stroke Width > 1
|
|
graphics_circle_quadrant_draw_stroked_non_aa(ctx, p, radius, stroke_width, quadrant);
|
|
} else {
|
|
// Non-Antialiased and Stroke Width == 1
|
|
graphics_circle_quadrant_draw_1px_non_aa(ctx, p, radius, quadrant);
|
|
}
|
|
}
|
|
|
|
T_STATIC void graphics_circle_draw_1px_non_aa(GContext* ctx, GPoint p, uint16_t radius) {
|
|
graphics_circle_quadrant_draw_1px_non_aa(ctx, p, radius, GCornersAll);
|
|
|
|
p.x += ctx->draw_state.drawing_box.origin.x;
|
|
p.y += ctx->draw_state.drawing_box.origin.y;
|
|
|
|
graphics_private_set_pixel(ctx, GPoint(p.x, p.y + radius));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x, p.y - radius));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x + radius, p.y));
|
|
graphics_private_set_pixel(ctx, GPoint(p.x - radius, p.y));
|
|
}
|
|
|
|
#if PBL_COLOR
|
|
T_STATIC void graphics_circle_draw_1px_aa(GContext* ctx, GPoint p, uint16_t radius) {
|
|
graphics_circle_quadrant_draw_1px_aa(ctx, p, radius, GCornersAll);
|
|
}
|
|
|
|
//! Draws an antialiased circle of stroke width > 1
|
|
//! @note This only supports odd numbers for stroke_width - even numbers will be rounded up.
|
|
//! Minimal supported stroke_width is 3
|
|
T_STATIC void graphics_circle_draw_stroked_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
uint8_t stroke_width) {
|
|
graphics_circle_quadrant_draw_stroked_aa(ctx, p, radius, stroke_width, GCornersAll);
|
|
}
|
|
#endif // PBL_COLOR
|
|
|
|
//! Draws a non-antialiased circle of stroke width > 1
|
|
//! @note This only supports odd numbers for stroke_width - even numbers will be rounded up.
|
|
//! Minimal supported stroke_width is 3
|
|
T_STATIC void graphics_circle_draw_stroked_non_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
uint8_t stroke_width) {
|
|
graphics_circle_quadrant_draw_stroked_non_aa(ctx, p, radius, stroke_width, GCornersAll);
|
|
}
|
|
|
|
// Lifted directly from: http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
|
|
void graphics_draw_circle(GContext* ctx, GPoint p, uint16_t radius) {
|
|
PBL_ASSERTN(ctx);
|
|
if (ctx->lock) {
|
|
return;
|
|
}
|
|
|
|
if (radius == 0) {
|
|
// Special case radius 0 to fill a circle with radius equal to half the stroke width
|
|
// Backup the fill color and set that to the current stroke color since the fill color
|
|
// is what is used for fill circle. Restore the fill color afterwards.
|
|
GColor backup_fill_color = ctx->draw_state.fill_color;
|
|
ctx->draw_state.fill_color = ctx->draw_state.stroke_color;
|
|
graphics_fill_circle(ctx, p, ctx->draw_state.stroke_width / 2);
|
|
ctx->draw_state.fill_color = backup_fill_color;
|
|
return;
|
|
}
|
|
|
|
#if PBL_COLOR
|
|
if (ctx->draw_state.antialiased) {
|
|
if (ctx->draw_state.stroke_width > 1) {
|
|
// Antialiased and Stroke Width > 1
|
|
graphics_circle_draw_stroked_aa(ctx, p, radius, ctx->draw_state.stroke_width);
|
|
return;
|
|
} else {
|
|
// Antialiased and Stroke Width == 1
|
|
graphics_circle_draw_1px_aa(ctx, p, radius);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
if (ctx->draw_state.stroke_width > 1) {
|
|
// Non-Antialiased and Stroke Width > 1
|
|
graphics_circle_draw_stroked_non_aa(ctx, p, radius, ctx->draw_state.stroke_width);
|
|
} else {
|
|
// Non-Antialiased and Stroke Width == 1
|
|
graphics_circle_draw_1px_non_aa(ctx, p, radius);
|
|
}
|
|
}
|
|
|
|
#if defined(PLATFORM_TINTIN)
|
|
NOINLINE // Save space on tintin
|
|
#else
|
|
ALWAYS_INLINE // Optimize for speed on other platforms
|
|
#endif
|
|
static void prv_fill_horizontal_line(GContext *ctx, GPoint p, int16_t width) {
|
|
graphics_fill_rect(ctx, &(GRect) { .origin = p, .size = { width, 1 } });
|
|
}
|
|
|
|
void graphics_circle_quadrant_fill_non_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
GCornerMask quadrant) {
|
|
const int16_t x0 = p.x;
|
|
const int16_t y0 = p.y;
|
|
int f = 1 - radius;
|
|
int ddF_x = 1;
|
|
int ddF_y = -2 * radius;
|
|
int x = 0;
|
|
uint16_t y = radius;
|
|
|
|
while (x < y) {
|
|
if (f >= 0) {
|
|
y--;
|
|
ddF_y += 2;
|
|
f += ddF_y;
|
|
}
|
|
|
|
x++;
|
|
ddF_x += 2;
|
|
f += ddF_x;
|
|
|
|
if (quadrant & GCornerBottomLeft) {
|
|
if (x == 1) {
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - radius, y0), radius + 1);
|
|
}
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - x, y0 + y), x + 1);
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - y, y0 + x), y + 1);
|
|
}
|
|
|
|
if (quadrant & GCornerBottomRight) {
|
|
if (x == 1) {
|
|
prv_fill_horizontal_line(ctx, GPoint(x0, y0), radius + 1);
|
|
}
|
|
prv_fill_horizontal_line(ctx, GPoint(x0, y0 + y), x + 1);
|
|
prv_fill_horizontal_line(ctx, GPoint(x0, y0 + x), y + 1);
|
|
}
|
|
|
|
if (quadrant & GCornerTopLeft) {
|
|
if (x == 1) {
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - radius, y0), radius + 1);
|
|
}
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - x, y0 - y), x + 1);
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - y, y0 - x), y + 1);
|
|
}
|
|
|
|
if (quadrant & GCornerTopRight) {
|
|
if (x == 1) {
|
|
prv_fill_horizontal_line(ctx, GPoint(x0, y0), radius + 1);
|
|
}
|
|
prv_fill_horizontal_line(ctx, GPoint(x0, y0 - y), x + 1);
|
|
prv_fill_horizontal_line(ctx, GPoint(x0, y0 - x), y + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void graphics_fill_half_circle(GContext* ctx, int x0, int y0, uint16_t radius,
|
|
uint8_t section) {
|
|
int f = 1 - radius;
|
|
int ddF_x = 1;
|
|
int ddF_y = -2 * radius;
|
|
int x = 0;
|
|
uint16_t y = radius;
|
|
|
|
while (x < y) {
|
|
if (f >= 0) {
|
|
y--;
|
|
ddF_y += 2;
|
|
f += ddF_y;
|
|
}
|
|
|
|
x++;
|
|
ddF_x += 2;
|
|
f += ddF_x;
|
|
|
|
// bottom
|
|
if (section & GCornersBottom) {
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - x, y0 + y), 2 * x + 1);
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - y, y0 + x), 2 * y + 1);
|
|
}
|
|
|
|
// top
|
|
if (section & GCornersTop) {
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - x, y0 - y), 2 * x + 1);
|
|
prv_fill_horizontal_line(ctx, GPoint(x0 - y, y0 - x), 2 * y + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
MOCKABLE void graphics_circle_fill_non_aa(GContext* ctx, GPoint p, uint16_t radius) {
|
|
prv_fill_horizontal_line(ctx, GPoint(p.x - radius, p.y), 2 * radius + 1);
|
|
graphics_fill_half_circle(ctx, p.x, p.y, radius, GCornersAll);
|
|
}
|
|
|
|
#if PBL_COLOR
|
|
MOCKABLE void graphics_internal_circle_quadrant_fill_aa(GContext* ctx, GPoint p, uint16_t radius,
|
|
GCornerMask quadrant) {
|
|
// Radius cannot be smaller than 1
|
|
PBL_ASSERTN(radius > 0);
|
|
|
|
prv_fill_oval_quadrant(ctx, p, radius, radius, 0, 0, quadrant);
|
|
}
|
|
#endif // PBL_COLOR
|
|
|
|
// Returns x for f(x) = g(x) with f(x)=y and a line g(x) that goes through two given points a,b
|
|
static int16_t prv_intersection_between_horizontal_and_line(Fixed_S16_3 progress, GPointPrecise top,
|
|
GPointPrecise bottom) {
|
|
if (bottom.y.raw_value - top.y.raw_value == 0) {
|
|
return top.x.raw_value + (bottom.x.raw_value - top.x.raw_value);
|
|
}
|
|
|
|
return top.x.raw_value + (bottom.x.raw_value - top.x.raw_value) *
|
|
(progress.raw_value - top.y.raw_value) /
|
|
(bottom.y.raw_value - top.y.raw_value);
|
|
}
|
|
|
|
static void prv_swap_precise_points(GPointPrecise *p0, GPointPrecise *p1) {
|
|
GPointPrecise tmp = *p0;
|
|
*p0 = *p1;
|
|
*p1 = tmp;
|
|
}
|
|
|
|
static void prv_draw_scanline_collision_points(GContext *ctx, int16_t y,
|
|
int16_t left, int16_t right,
|
|
int16_t starting_edge, int16_t ending_edge,
|
|
bool ignore_close_angles) {
|
|
if (starting_edge > ending_edge || (ignore_close_angles && starting_edge == ending_edge)) {
|
|
// Two separate drawings...
|
|
starting_edge = MAX(starting_edge, left);
|
|
ending_edge = MIN(ending_edge, right);
|
|
|
|
if (left <= ending_edge) {
|
|
graphics_private_draw_horizontal_line(ctx, y,
|
|
(Fixed_S16_3){.raw_value = left},
|
|
(Fixed_S16_3){.raw_value = ending_edge -
|
|
FIXED_S16_3_ONE.raw_value});
|
|
}
|
|
|
|
if (starting_edge <= right) {
|
|
graphics_private_draw_horizontal_line(ctx, y,
|
|
(Fixed_S16_3){.raw_value = starting_edge},
|
|
(Fixed_S16_3){.raw_value = right -
|
|
FIXED_S16_3_ONE.raw_value});
|
|
}
|
|
} else {
|
|
starting_edge = (MAX(left, starting_edge));
|
|
ending_edge = (MIN(right, ending_edge));
|
|
|
|
if (starting_edge <= ending_edge) {
|
|
graphics_private_draw_horizontal_line(ctx, y,
|
|
(Fixed_S16_3){.raw_value = starting_edge},
|
|
(Fixed_S16_3){.raw_value = ending_edge -
|
|
FIXED_S16_3_ONE.raw_value});
|
|
}
|
|
}
|
|
}
|
|
|
|
static GCornerMask prv_get_full_quadrants(int8_t starting_quadrant, int8_t ending_quadrant) {
|
|
GCornerMask quadrants_solid = GCornerNone;
|
|
|
|
if (starting_quadrant >= ending_quadrant) {
|
|
ending_quadrant += QUADRANTS_NUM;
|
|
}
|
|
|
|
for (int i = starting_quadrant + 1; i < ending_quadrant; i++) {
|
|
quadrants_solid = quadrants_solid | radius_quadrants[i % QUADRANTS_NUM];
|
|
}
|
|
|
|
return quadrants_solid;
|
|
}
|
|
|
|
static void prv_get_angles_mask_edge(Fixed_S16_3 y, GPointPrecise center, GCornerMask quadrant,
|
|
int16_t *top_edge, int16_t *bottom_edge,
|
|
GPointPrecise top, GPointPrecise bottom) {
|
|
// This function determines where scanline is in position to the angle line and sets
|
|
// angle masking values accordingly
|
|
if (quadrant & GCornersTop) {
|
|
if (center.y.raw_value - y.raw_value <= bottom.y.raw_value) {
|
|
if (center.y.raw_value - y.raw_value >= top.y.raw_value) {
|
|
*top_edge = prv_intersection_between_horizontal_and_line((Fixed_S16_3){.raw_value =
|
|
(center.y.raw_value - y.raw_value)}, top, bottom);
|
|
} else {
|
|
*top_edge = top.x.raw_value;
|
|
}
|
|
} else {
|
|
*top_edge = bottom.x.raw_value;
|
|
}
|
|
} else {
|
|
if (center.y.raw_value + y.raw_value >= top.y.raw_value) {
|
|
if (center.y.raw_value + y.raw_value <= bottom.y.raw_value) {
|
|
*bottom_edge = prv_intersection_between_horizontal_and_line((Fixed_S16_3){.raw_value =
|
|
(center.y.raw_value + y.raw_value)}, top, bottom);
|
|
} else {
|
|
*bottom_edge = bottom.x.raw_value;
|
|
}
|
|
} else {
|
|
*bottom_edge = top.x.raw_value;
|
|
}
|
|
}
|
|
}
|
|
|
|
T_STATIC EllipsisDrawConfig prv_calc_draw_config_ellipsis(int32_t angle_start, int32_t angle_end) {
|
|
PBL_ASSERTN(angle_start <= angle_end);
|
|
|
|
EllipsisDrawConfig config = (EllipsisDrawConfig){(EllipsisPartDrawConfig){0, GCornerNone},
|
|
GCornerNone,
|
|
(EllipsisPartDrawConfig){0, GCornerNone}};
|
|
// Nothing to draw case:
|
|
if (angle_end == angle_start) {
|
|
return config;
|
|
}
|
|
|
|
// Full circle case:
|
|
if (angle_end - angle_start >= TRIG_MAX_ANGLE) {
|
|
config.full_quadrants = GCornersAll;
|
|
return config;
|
|
}
|
|
|
|
int32_t angle_start_normalized = normalize_angle(angle_start);
|
|
int32_t angle_end_normalized = normalize_angle(angle_end);
|
|
|
|
int8_t starting_quadrant_normalized = (angle_start_normalized / QUADRANT_ANGLE) % 4;
|
|
int8_t ending_quadrant_normalized = (angle_end_normalized / QUADRANT_ANGLE) % 4;
|
|
|
|
if (starting_quadrant_normalized == ending_quadrant_normalized) {
|
|
// Both indicate same quadrant...
|
|
config.start_quadrant.angle = angle_start_normalized;
|
|
config.start_quadrant.quadrant = radius_quadrants[starting_quadrant_normalized];
|
|
config.end_quadrant.angle = angle_end_normalized;
|
|
config.end_quadrant.quadrant = radius_quadrants[ending_quadrant_normalized];
|
|
|
|
if (angle_end - angle_start > QUADRANT_ANGLE) {
|
|
// Full quadrants:
|
|
config.full_quadrants = prv_get_full_quadrants(starting_quadrant_normalized,
|
|
ending_quadrant_normalized);
|
|
}
|
|
} else {
|
|
// Angles in different quadrants
|
|
config.start_quadrant.angle = angle_start_normalized;
|
|
config.start_quadrant.quadrant = radius_quadrants[starting_quadrant_normalized];
|
|
config.end_quadrant.angle = angle_end_normalized;
|
|
config.end_quadrant.quadrant = radius_quadrants[ending_quadrant_normalized];
|
|
|
|
if (angle_start % QUADRANT_ANGLE == 0) {
|
|
starting_quadrant_normalized--;
|
|
}
|
|
|
|
// Full quadrants
|
|
config.full_quadrants = prv_get_full_quadrants(starting_quadrant_normalized,
|
|
ending_quadrant_normalized);
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
static void prv_fill_oval_precise(GContext *ctx, GPointPrecise center,
|
|
Fixed_S16_3 radius_outer_x, Fixed_S16_3 radius_outer_y,
|
|
Fixed_S16_3 radius_inner_x, Fixed_S16_3 radius_inner_y,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
|
|
// Drawing config calculation
|
|
const EllipsisDrawConfig config = prv_calc_draw_config_ellipsis(angle_start, angle_end);
|
|
|
|
// This flag will skip calculation of angles
|
|
const bool is_full_circle = config.full_quadrants == GCornersAll;
|
|
|
|
// This will indicate special line in the middle of the circle, when center of the circle
|
|
// lies in between lines
|
|
const bool odd_line = (center.y.fraction == FIXED_S16_3_HALF.raw_value);
|
|
|
|
// This will prevent rounding error from breaking the scanline when angles are on same side but
|
|
// in reversed order (scanline first hits end angle then hits start angle
|
|
const bool ignore_close_angles = (angle_end - angle_start > (TRIG_MAX_ANGLE / 2));
|
|
|
|
// Clipping insets to prevent negative values
|
|
radius_inner_x.raw_value = MAX(radius_inner_x.raw_value, 0);
|
|
radius_inner_y.raw_value = MAX(radius_inner_y.raw_value, 0);
|
|
|
|
// This flag prevents from calculation of the inner circle (and bugs related to it)
|
|
const bool no_inner_ellipsis = (radius_inner_x.raw_value == 0 || radius_inner_y.raw_value == 0);
|
|
|
|
// Squared radiuses values - they're used a lot in some cases
|
|
const uint32_t radius_outer_x_sq =
|
|
(radius_outer_x.raw_value * radius_outer_x.raw_value) >> FIXED_S16_3_PRECISION;
|
|
const uint32_t radius_outer_y_sq =
|
|
(radius_outer_y.raw_value * radius_outer_y.raw_value) >> FIXED_S16_3_PRECISION;
|
|
const uint32_t radius_inner_x_sq =
|
|
(radius_inner_x.raw_value * radius_inner_x.raw_value) >> FIXED_S16_3_PRECISION;
|
|
const uint32_t radius_inner_y_sq =
|
|
(radius_inner_y.raw_value * radius_inner_y.raw_value) >> FIXED_S16_3_PRECISION;
|
|
|
|
// Intersection points of angles and radiuses
|
|
GPointPrecise start_top =
|
|
prv_get_rotated_precise_point_for_ellipsis(center,
|
|
radius_outer_x.raw_value, radius_outer_y.raw_value,
|
|
config.start_quadrant.angle);
|
|
GPointPrecise end_top =
|
|
prv_get_rotated_precise_point_for_ellipsis(center,
|
|
radius_outer_x.raw_value, radius_outer_y.raw_value,
|
|
config.end_quadrant.angle);
|
|
|
|
GPointPrecise start_bottom = (no_inner_ellipsis) ? center :
|
|
prv_get_rotated_precise_point_for_ellipsis(center,
|
|
radius_inner_x.raw_value, radius_inner_y.raw_value,
|
|
config.start_quadrant.angle);
|
|
GPointPrecise end_bottom = (no_inner_ellipsis) ? center :
|
|
prv_get_rotated_precise_point_for_ellipsis(center,
|
|
radius_inner_x.raw_value, radius_inner_y.raw_value,
|
|
config.end_quadrant.angle);
|
|
|
|
// Swapping top/bottom offset points if necessary
|
|
if (start_top.y.raw_value > start_bottom.y.raw_value) {
|
|
prv_swap_precise_points(&start_top, &start_bottom);
|
|
} else if (start_top.y.raw_value == start_bottom.y.raw_value &&
|
|
(config.start_quadrant.quadrant & GCornersBottom)) {
|
|
// Special case to make bottom edge to be on left side and keep masking algorithm happy
|
|
prv_swap_precise_points(&start_top, &start_bottom);
|
|
}
|
|
|
|
if (end_top.y.raw_value > end_bottom.y.raw_value) {
|
|
prv_swap_precise_points(&end_top, &end_bottom);
|
|
} else if (end_top.y.raw_value == end_bottom.y.raw_value &&
|
|
(config.end_quadrant.quadrant & GCornersBottom)) {
|
|
// Special case to make bottom edge to be on left side and keep masking algorithm happy
|
|
prv_swap_precise_points(&end_top, &end_bottom);
|
|
}
|
|
|
|
// Range for scanline, since scanlines are mirrored from the middle of the circle this is also
|
|
// indicated from the middle, therefore initialised with 0 (as middle) and
|
|
// radius_y (as scanlines are on y axis)
|
|
int draw_min = 0;
|
|
int draw_max = radius_outer_y.integer;
|
|
|
|
// Adjust to drawing_box offset
|
|
int adjusted_center = center.y.integer + ctx->draw_state.drawing_box.origin.y;
|
|
// We add one to compensate in case of odd line needs to be drawn
|
|
int adjusted_top = adjusted_center - radius_outer_y.integer - 1;
|
|
int adjusted_bottom = adjusted_center + radius_outer_y.integer + 1;
|
|
|
|
// Clip_box
|
|
GRect clip_box = ctx->draw_state.clip_box;
|
|
|
|
// Clip adjusted values by clip_box
|
|
adjusted_top = MAX(adjusted_top, clip_box.origin.y);
|
|
adjusted_top = MIN(adjusted_top, clip_box.origin.y + clip_box.size.h);
|
|
adjusted_bottom = MAX(adjusted_bottom, clip_box.origin.y);
|
|
adjusted_bottom = MIN(adjusted_bottom, clip_box.origin.y + clip_box.size.h);
|
|
|
|
// Remove offset from adjusted values
|
|
adjusted_top -= ctx->draw_state.drawing_box.origin.y;
|
|
adjusted_bottom -= ctx->draw_state.drawing_box.origin.y;
|
|
|
|
// Calculate distances from the middle of the circle (discard negative values)
|
|
int draw_max_top = MAX(center.y.integer - adjusted_top, 0);
|
|
int draw_max_bottom = MAX(adjusted_bottom - center.y.integer, 0);
|
|
int draw_min_top = MAX(center.y.integer - adjusted_bottom, 0);
|
|
// In case of odd line, center is with half pixel so we have to subtract one more full line
|
|
int draw_min_bottom = MAX(adjusted_top - center.y.integer - 1, 0);
|
|
|
|
// Apply clipped distances
|
|
draw_max = MIN(draw_max, MAX(draw_max_top, draw_max_bottom));
|
|
draw_min = MAX(draw_min, MAX(draw_min_top, draw_min_bottom));
|
|
|
|
// Scanline offset in precise point for calculation of edges
|
|
Fixed_S16_3 y = (Fixed_S16_3){.integer = draw_min};
|
|
|
|
// Flags used for filling of solid parts of the circle, when angles are not intersecting them
|
|
const bool draw_top = is_full_circle || (config.full_quadrants & GCornersTop ||
|
|
config.start_quadrant.quadrant & GCornersTop ||
|
|
config.end_quadrant.quadrant & GCornersTop);
|
|
const bool draw_bottom = is_full_circle || (config.full_quadrants & GCornersBottom ||
|
|
config.start_quadrant.quadrant & GCornersBottom ||
|
|
(config.end_quadrant.quadrant & GCornersBottom &&
|
|
config.end_quadrant.angle % QUADRANT_ANGLE != 0));
|
|
|
|
// Offsets for mirroring of scanline (they differ for even and odd height of the circle)
|
|
int special_line_offset_top = 1;
|
|
int special_line_offset_bottom = 1;
|
|
|
|
// Color hack
|
|
const GColor stroke_color = ctx->draw_state.stroke_color;
|
|
ctx->draw_state.stroke_color = ctx->draw_state.fill_color;
|
|
|
|
// In case of odd line in the middle, this is where we draw it:
|
|
if (odd_line) {
|
|
int16_t starting_edge = center.x.raw_value - radius_outer_x.raw_value;
|
|
int16_t ending_edge = center.x.raw_value + radius_outer_x.raw_value;
|
|
|
|
if (!is_full_circle) {
|
|
// Following code finds crossing points slightly above and below center point
|
|
// as the radial angles will appear only with one of these cases, other
|
|
// will return same starting/ending point, which might be invalid angle
|
|
// We might also reject this data if delta of start/end angle is bigger than 180°
|
|
Fixed_S16_3 y_middle = (Fixed_S16_3){.raw_value = y.raw_value - 4};
|
|
prv_get_angles_mask_edge(y_middle, center, config.start_quadrant.quadrant,
|
|
&starting_edge, &ending_edge, start_top, start_bottom);
|
|
prv_get_angles_mask_edge(y_middle, center, config.end_quadrant.quadrant,
|
|
&ending_edge, &starting_edge, end_top, end_bottom);
|
|
|
|
// Now we use only one with valuable data - same starting/ending is rejected
|
|
if (starting_edge == ending_edge) {
|
|
y_middle.integer++;
|
|
|
|
prv_get_angles_mask_edge(y_middle, center, config.start_quadrant.quadrant,
|
|
&starting_edge, &ending_edge, start_top, start_bottom);
|
|
prv_get_angles_mask_edge(y_middle, center, config.end_quadrant.quadrant,
|
|
&ending_edge, &starting_edge, end_top, end_bottom);
|
|
}
|
|
}
|
|
|
|
int16_t outer_edge = prv_get_ellipsis_border(y, radius_outer_y_sq, radius_outer_x_sq).raw_value;
|
|
int16_t left = center.x.raw_value - outer_edge;
|
|
int16_t right = center.x.raw_value + outer_edge;
|
|
|
|
if (!no_inner_ellipsis && radius_inner_y.integer != 0) {
|
|
// This complicates the situation
|
|
int16_t inner_edge =
|
|
prv_get_ellipsis_border(y, radius_inner_y_sq, radius_inner_x_sq).raw_value;
|
|
|
|
int16_t inner_left = center.x.raw_value - inner_edge;
|
|
int16_t inner_right = center.x.raw_value + inner_edge;
|
|
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer, left, inner_left,
|
|
starting_edge, ending_edge, ignore_close_angles);
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer, inner_right, right,
|
|
starting_edge, ending_edge, ignore_close_angles);
|
|
} else {
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer, left, right,
|
|
starting_edge, ending_edge, ignore_close_angles);
|
|
}
|
|
|
|
// After drawing the line we move scanline edge calculation offset
|
|
y.integer++;
|
|
} else {
|
|
// If theres no line in the middle, we move edge calculation offset by half (to stay
|
|
// in the middle of the pixel) and change bottom offset to zero (to evenly mirror lines)
|
|
y.fraction = 4;
|
|
special_line_offset_bottom = 0;
|
|
}
|
|
|
|
// Main drawing loop
|
|
for (int i=draw_min; i < draw_max; i++, y.integer++) {
|
|
// Separate min/max offsets for angles masking for lines drawn above and below center point
|
|
int16_t top_starting_edge = center.x.raw_value - radius_outer_x.raw_value;
|
|
int16_t top_ending_edge = center.x.raw_value + radius_outer_x.raw_value;
|
|
int16_t bottom_starting_edge = top_starting_edge;
|
|
int16_t bottom_ending_edge = top_ending_edge;
|
|
|
|
// If the circle is not full - calculate mask for angles
|
|
if (!is_full_circle) {
|
|
prv_get_angles_mask_edge(y, center, config.start_quadrant.quadrant,
|
|
&top_starting_edge, &bottom_ending_edge, start_top, start_bottom);
|
|
prv_get_angles_mask_edge(y, center, config.end_quadrant.quadrant,
|
|
&top_ending_edge, &bottom_starting_edge, end_top, end_bottom);
|
|
}
|
|
|
|
// Calculate outer radius edges
|
|
int16_t outer_edge = prv_get_ellipsis_border(y, radius_outer_y_sq, radius_outer_x_sq).raw_value;
|
|
int16_t left = center.x.raw_value - outer_edge;
|
|
int16_t right = center.x.raw_value + outer_edge;
|
|
|
|
// If theres circle in the middle - calculate it:
|
|
if (!no_inner_ellipsis && i < radius_inner_y.integer) {
|
|
int16_t inner_edge =
|
|
prv_get_ellipsis_border(y, radius_inner_y_sq, radius_inner_x_sq).raw_value;
|
|
|
|
int16_t inner_left = center.x.raw_value - inner_edge;
|
|
int16_t inner_right = center.x.raw_value + inner_edge;
|
|
|
|
// Using top/bottom flags we make sure to not draw outside of given angles range,
|
|
// we also draw separately left and right side of the circle
|
|
if (draw_top) {
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer - i - special_line_offset_top,
|
|
left, inner_left, top_starting_edge, top_ending_edge,
|
|
ignore_close_angles);
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer - i - special_line_offset_top,
|
|
inner_right, right, top_starting_edge, top_ending_edge,
|
|
ignore_close_angles);
|
|
}
|
|
|
|
if (draw_bottom) {
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer + i + special_line_offset_bottom,
|
|
left, inner_left, bottom_starting_edge,
|
|
bottom_ending_edge, ignore_close_angles);
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer + i + special_line_offset_bottom,
|
|
inner_right, right, bottom_starting_edge,
|
|
bottom_ending_edge, ignore_close_angles);
|
|
}
|
|
} else {
|
|
// If theres nothing in the middle, draw top and bottom parts of the circle
|
|
if (draw_top) {
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer - i - special_line_offset_top,
|
|
left, right, top_starting_edge, top_ending_edge,
|
|
ignore_close_angles);
|
|
}
|
|
|
|
if (draw_bottom) {
|
|
prv_draw_scanline_collision_points(ctx, center.y.integer + i + special_line_offset_bottom,
|
|
left, right, bottom_starting_edge, bottom_ending_edge,
|
|
ignore_close_angles);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finish color hack
|
|
ctx->draw_state.stroke_color = stroke_color;
|
|
}
|
|
|
|
void prv_fill_oval_in_rect(GContext *ctx, GRect rect, uint16_t inset_x, uint16_t inset_y,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
// Cast to precision point
|
|
GPointPrecise center = GPointPrecise(((rect.origin.x << FIXED_S16_3_PRECISION) +
|
|
(rect.size.w << FIXED_S16_3_PRECISION) / 2),
|
|
((rect.origin.y << FIXED_S16_3_PRECISION) +
|
|
(rect.size.h << FIXED_S16_3_PRECISION) / 2));
|
|
|
|
Fixed_S16_3 radius_outer_x = (Fixed_S16_3){.raw_value =
|
|
((rect.size.w << FIXED_S16_3_PRECISION) / 2)};
|
|
Fixed_S16_3 radius_outer_y = (Fixed_S16_3){.raw_value =
|
|
((rect.size.h << FIXED_S16_3_PRECISION) / 2)};
|
|
Fixed_S16_3 radius_inner_x = (Fixed_S16_3){.raw_value =
|
|
(((rect.size.w - inset_x * 2) << FIXED_S16_3_PRECISION) / 2)};
|
|
Fixed_S16_3 radius_inner_y = (Fixed_S16_3){.raw_value =
|
|
(((rect.size.h - inset_y * 2) << FIXED_S16_3_PRECISION) / 2)};
|
|
|
|
prv_fill_oval_precise(ctx, center, radius_outer_x, radius_outer_y,
|
|
radius_inner_x, radius_inner_y, angle_start, angle_end);
|
|
}
|
|
|
|
void prv_fill_oval(GContext *ctx, GPoint center, uint16_t outer_radius_x, uint16_t outer_radius_y,
|
|
uint16_t inner_radius_x, uint16_t inner_radius_y,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
const GPointPrecise center_precise = (GPointPrecise) {
|
|
.x.integer = center.x, .x.fraction = FIXED_S16_3_HALF.raw_value,
|
|
.y.integer = center.y, .y.fraction = FIXED_S16_3_HALF.raw_value,
|
|
};
|
|
|
|
const Fixed_S16_3 outer_x_precise = (Fixed_S16_3){
|
|
.integer = outer_radius_x,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
const Fixed_S16_3 outer_y_precise = (Fixed_S16_3){
|
|
.integer = outer_radius_y,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
const Fixed_S16_3 inner_x_precise = (Fixed_S16_3){
|
|
.integer = inner_radius_x,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
const Fixed_S16_3 inner_y_precise = (Fixed_S16_3){
|
|
.integer = inner_radius_y,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
|
|
prv_fill_oval_precise(ctx, center_precise, outer_x_precise, outer_y_precise,
|
|
inner_x_precise, inner_y_precise, angle_start, angle_end);
|
|
}
|
|
|
|
void prv_fill_oval_quadrant_precise(GContext *ctx, GPointPrecise point,
|
|
Fixed_S16_3 outer_radius_x, Fixed_S16_3 outer_radius_y,
|
|
Fixed_S16_3 inner_radius_x, Fixed_S16_3 inner_radius_y,
|
|
GCornerMask quadrant) {
|
|
// Translate quadrants to angles
|
|
if (quadrant == GCornerNone) {
|
|
return;
|
|
}
|
|
|
|
// Calculate quadrants...
|
|
int32_t angle_start = 0;
|
|
int32_t angle_end = 0;
|
|
|
|
if (quadrant == GCornersAll) {
|
|
angle_end = TRIG_MAX_ANGLE;
|
|
} else {
|
|
if (quadrant & radius_quadrants[0]) {
|
|
for (int i=QUADRANTS_NUM - 1; i >= 0; i--) {
|
|
if (!(quadrant & radius_quadrants[i])) {
|
|
angle_start = ((i + 1) % 4) * (TRIG_MAX_ANGLE / 4);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (int i=1; i <= QUADRANTS_NUM; i++) {
|
|
if (quadrant & radius_quadrants[i % 4]) {
|
|
angle_start = i * (TRIG_MAX_ANGLE / 4);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(quadrant & radius_quadrants[0])) {
|
|
for (int i=QUADRANTS_NUM - 1; i >= 0; i--) {
|
|
if (quadrant & radius_quadrants[i]) {
|
|
angle_end = ((i + 1) % 4) * (TRIG_MAX_ANGLE / 4);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (int i=1; i <= QUADRANTS_NUM; i++) {
|
|
if (!(quadrant & radius_quadrants[i % 4])) {
|
|
angle_end = i * (TRIG_MAX_ANGLE / 4);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (angle_end <= angle_start) {
|
|
angle_end += TRIG_MAX_ANGLE;
|
|
}
|
|
|
|
prv_fill_oval_precise(ctx, point, outer_radius_x, outer_radius_y,
|
|
inner_radius_x, inner_radius_y, angle_start, angle_end);
|
|
}
|
|
|
|
void prv_fill_oval_quadrant(GContext *ctx, GPoint point,
|
|
uint16_t outer_radius_x, uint16_t outer_radius_y,
|
|
uint16_t inner_radius_x, uint16_t inner_radius_y,
|
|
GCornerMask quadrant) {
|
|
// Cast to precision point
|
|
const GPointPrecise center_precise = (GPointPrecise) {
|
|
.x.integer = point.x, .x.fraction = FIXED_S16_3_HALF.raw_value,
|
|
.y.integer = point.y, .y.fraction = FIXED_S16_3_HALF.raw_value,
|
|
};
|
|
|
|
const Fixed_S16_3 outer_x_precise = (Fixed_S16_3){
|
|
.integer = outer_radius_x,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
const Fixed_S16_3 outer_y_precise = (Fixed_S16_3){
|
|
.integer = outer_radius_y,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
const Fixed_S16_3 inner_x_precise = (Fixed_S16_3){
|
|
.integer = inner_radius_x,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
const Fixed_S16_3 inner_y_precise = (Fixed_S16_3){
|
|
.integer = inner_radius_y,
|
|
.fraction = FIXED_S16_3_HALF.raw_value
|
|
};
|
|
|
|
prv_fill_oval_quadrant_precise(ctx, center_precise, outer_x_precise, outer_y_precise,
|
|
inner_x_precise, inner_y_precise, quadrant);
|
|
}
|
|
|
|
MOCKABLE void graphics_draw_arc_precise_internal(GContext *ctx, GPointPrecise center,
|
|
Fixed_S16_3 radius,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
uint16_t stroke_width = ctx->draw_state.stroke_width;
|
|
|
|
// We accept only .0 and .5 precision for now:
|
|
center.x.raw_value -= center.x.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
center.y.raw_value -= center.y.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
|
|
// To maintain compatibility we have to adjust from integral points where given point means
|
|
// center of the point
|
|
center.x.raw_value += 4;
|
|
center.y.raw_value += 4;
|
|
radius.raw_value += 4;
|
|
|
|
// Same for radius:
|
|
radius.raw_value -= radius.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
|
|
if (stroke_width < 1 || angle_start > angle_end) {
|
|
// Dont draw anything
|
|
return;
|
|
}
|
|
// TODO: Adapt this to support new precision, for now this is faked by regular fill
|
|
// This will be done with PBL-24777
|
|
/*
|
|
else if (stroke_width == 1) {
|
|
prv_circle_arc_draw_1px(ctx, center, radius, angle_start, angle_end);
|
|
return;
|
|
}
|
|
*/
|
|
// Color hack to draw using stroke_color instead of fill_color
|
|
GColor tmp_color = ctx->draw_state.fill_color;
|
|
ctx->draw_state.fill_color = ctx->draw_state.stroke_color;
|
|
|
|
Fixed_S16_3 half_stroke_width =
|
|
(Fixed_S16_3){.raw_value = (ctx->draw_state.stroke_width << FIXED_S16_3_PRECISION) / 2};
|
|
Fixed_S16_3 radius_inner =
|
|
(Fixed_S16_3){.raw_value = MAX(0, radius.raw_value - half_stroke_width.raw_value)};
|
|
Fixed_S16_3 radius_outer =
|
|
(Fixed_S16_3){.raw_value = radius.raw_value + half_stroke_width.raw_value};
|
|
|
|
if (radius_outer.integer > 0) {
|
|
prv_fill_oval_precise(ctx, center, radius_outer, radius_outer, radius_inner, radius_inner,
|
|
angle_start, angle_end);
|
|
|
|
if (half_stroke_width.integer >= 1) {
|
|
GPointPrecise starting_point =
|
|
prv_get_rotated_precise_point(center, radius.raw_value, angle_start);
|
|
GPointPrecise ending_point =
|
|
prv_get_rotated_precise_point(center, radius.raw_value, angle_end);
|
|
|
|
prv_fill_oval_precise(ctx, starting_point, half_stroke_width, half_stroke_width,
|
|
FIXED_S16_3_ZERO, FIXED_S16_3_ZERO, 0, TRIG_MAX_ANGLE);
|
|
prv_fill_oval_precise(ctx, ending_point, half_stroke_width, half_stroke_width,
|
|
FIXED_S16_3_ZERO, FIXED_S16_3_ZERO, 0, TRIG_MAX_ANGLE);
|
|
}
|
|
}
|
|
|
|
// Restore color
|
|
ctx->draw_state.fill_color = tmp_color;
|
|
}
|
|
|
|
void graphics_draw_arc_internal(GContext *ctx, GPoint center, uint16_t radius, int32_t angle_start,
|
|
int32_t angle_end) {
|
|
// We're just casting this to precise points
|
|
GPointPrecise fixed_center;
|
|
|
|
// GPointPreciseFromGPoint doesnt work for unit tests (!!!)
|
|
fixed_center.x.integer = center.x;
|
|
fixed_center.y.integer = center.y;
|
|
fixed_center.x.fraction = 0;
|
|
fixed_center.y.fraction = 0;
|
|
Fixed_S16_3 fixed_radius = (Fixed_S16_3){.integer = radius, .fraction = 0};
|
|
|
|
graphics_draw_arc_precise_internal(ctx, fixed_center, fixed_radius, angle_start, angle_end);
|
|
}
|
|
|
|
void graphics_draw_arc(GContext *ctx, GRect rect, GOvalScaleMode scale_mode,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
GPointPrecise center;
|
|
Fixed_S16_3 radius;
|
|
grect_polar_calc_values(&rect, scale_mode, ¢er, &radius);
|
|
graphics_draw_arc_precise_internal(ctx, center, radius, angle_start, angle_end);
|
|
}
|
|
|
|
MOCKABLE 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) {
|
|
// This function is going to be replaced with function that will support drawing of ellipsis
|
|
// as documented in PBL-23640
|
|
|
|
// For now we only accept .0 and .5 radius precision
|
|
radius_inner.raw_value -= radius_inner.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
radius_outer.raw_value -= radius_outer.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
|
|
// Same goes for coordinates of center point:
|
|
center.x.raw_value -= center.x.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
center.y.raw_value -= center.y.raw_value % (FIXED_S16_3_ONE.raw_value / 2);
|
|
|
|
// Move the values to match old precision with integral coordinate between pixels
|
|
center.x.raw_value += 4;
|
|
center.y.raw_value += 4;
|
|
radius_inner.raw_value += 4;
|
|
radius_outer.raw_value += 4;
|
|
|
|
if (angle_start > angle_end || radius_outer.raw_value < radius_inner.raw_value) {
|
|
// Nothing will be drawn...
|
|
return;
|
|
}
|
|
|
|
// TODO: Adapt this to support new precision, for now this is faked by regular fill
|
|
// This will be done with PBL-24777
|
|
/*
|
|
if (radius_outer.raw_value - radius_inner.raw_value == FIXED_S16_3_ONE.raw_value) {
|
|
// Color hack
|
|
GColor tmp_color = ctx->draw_state.stroke_color;
|
|
ctx->draw_state.stroke_color = ctx->draw_state.fill_color;
|
|
|
|
// prv_circle_arc_draw_1px(ctx, center, radius_outer, angle_start, angle_end);
|
|
|
|
// Restore color
|
|
ctx->draw_state.stroke_color = tmp_color;
|
|
|
|
// We're done here
|
|
return;
|
|
} else
|
|
*/
|
|
if (radius_outer.raw_value - radius_inner.raw_value < FIXED_S16_3_ONE.raw_value) {
|
|
// Abort, abort!
|
|
return;
|
|
}
|
|
|
|
prv_fill_oval_precise(ctx, center, radius_outer, radius_outer,
|
|
radius_inner, radius_inner, angle_start, angle_end);
|
|
|
|
}
|
|
|
|
void graphics_fill_radial_internal(GContext *ctx, GPoint center, uint16_t radius_inner,
|
|
uint16_t radius_outer, int32_t angle_start, int32_t angle_end) {
|
|
// Cast given values to precise point
|
|
GPointPrecise center_fixed;
|
|
center_fixed.x.integer = center.x;
|
|
center_fixed.y.integer = center.y;
|
|
center_fixed.x.fraction = 0;
|
|
center_fixed.y.fraction = 0;
|
|
Fixed_S16_3 radius_inner_fixed = (Fixed_S16_3){.integer = radius_inner, .fraction = 0};
|
|
Fixed_S16_3 radius_outer_fixed = (Fixed_S16_3){.integer = radius_outer, .fraction = 0};
|
|
|
|
prv_fill_oval_precise(ctx, center_fixed, radius_outer_fixed, radius_outer_fixed,
|
|
radius_inner_fixed, radius_inner_fixed, angle_start, angle_end);
|
|
}
|
|
|
|
void graphics_fill_radial(GContext *ctx, GRect rect, GOvalScaleMode scale_mode,
|
|
uint16_t inset_thickness,
|
|
int32_t angle_start, int32_t angle_end) {
|
|
GPointPrecise center;
|
|
Fixed_S16_3 radius_outer;
|
|
grect_polar_calc_values(&rect, scale_mode, ¢er, &radius_outer);
|
|
const Fixed_S16_3 radius_inner =
|
|
Fixed_S16_3(radius_outer.raw_value - inset_thickness * FIXED_S16_3_ONE.raw_value);
|
|
graphics_fill_radial_precise_internal(ctx, center, radius_inner, radius_outer,
|
|
angle_start, angle_end);
|
|
}
|
|
|
|
void graphics_fill_oval(GContext *ctx, GRect rect, GOvalScaleMode scale_mode) {
|
|
const int32_t inset_thickness = MAX(ABS(rect.size.h), ABS(rect.size.w));
|
|
// Fill radial doesn't mind overlarge inset thickness
|
|
graphics_fill_radial(ctx, rect, scale_mode, inset_thickness, 0, TRIG_MAX_ANGLE);
|
|
}
|
|
|
|
void graphics_fill_circle(GContext* ctx, GPoint p, uint16_t radius) {
|
|
PBL_ASSERTN(ctx);
|
|
if (ctx->lock) {
|
|
return;
|
|
}
|
|
|
|
if (radius == 0) {
|
|
// Filling a circle of radius zero should just draw a single pixel.
|
|
// Backup the stroke color and set that to the current fill color since the stroke color
|
|
// is what is used for draw pixel. Restore the stroke color afterwards.
|
|
GColor backup_stroke_color = ctx->draw_state.stroke_color;
|
|
ctx->draw_state.stroke_color = ctx->draw_state.fill_color;
|
|
graphics_draw_pixel(ctx, p);
|
|
ctx->draw_state.stroke_color = backup_stroke_color;
|
|
return;
|
|
}
|
|
|
|
#if PBL_COLOR
|
|
if (ctx->draw_state.antialiased) {
|
|
graphics_internal_circle_quadrant_fill_aa(ctx, p, radius, GCornersAll);
|
|
return;
|
|
}
|
|
#endif
|
|
graphics_circle_fill_non_aa(ctx, p, radius);
|
|
}
|
|
|
|
GPointPrecise gpoint_from_polar_precise(const GPointPrecise *precise_center,
|
|
uint16_t precise_radius, int32_t angle) {
|
|
const uint32_t normalized_angle = normalize_angle(angle);
|
|
const GPointPrecise precise_perimeter_point = prv_get_rotated_precise_point(*precise_center,
|
|
precise_radius,
|
|
normalized_angle);
|
|
return precise_perimeter_point;
|
|
}
|
|
|
|
GPoint gpoint_from_polar_internal(const GPoint *center, uint16_t radius, int32_t angle) {
|
|
if (!center) {
|
|
return GPointZero;
|
|
}
|
|
|
|
const GPointPrecise precise_center = GPointPreciseFromGPoint((*center));
|
|
const uint16_t precise_radius = (uint16_t)(radius << GPOINT_PRECISE_PRECISION);
|
|
const GPointPrecise result = gpoint_from_polar_precise(&precise_center, precise_radius,
|
|
angle);
|
|
return GPointFromGPointPrecise(result);
|
|
}
|
|
|
|
|
|
GPointPrecise prv_gpointprecise_from_polar(GRect *rect, GOvalScaleMode scale_mode,
|
|
int32_t angle) {
|
|
GPointPrecise center;
|
|
Fixed_S16_3 radius;
|
|
grect_polar_calc_values(rect, scale_mode, ¢er, &radius);
|
|
return gpoint_from_polar_precise(¢er, radius.raw_value, angle);
|
|
}
|
|
|
|
GPoint gpoint_from_polar(GRect rect, GOvalScaleMode scale_mode, int32_t angle) {
|
|
const GPointPrecise result = prv_gpointprecise_from_polar(&rect, scale_mode, angle);
|
|
return GPointFromGPointPrecise(result);
|
|
}
|
|
|
|
GRect grect_centered_internal(const GPointPrecise *center, GSize size) {
|
|
size.w = ABS(size.w);
|
|
size.h = ABS(size.h);
|
|
const int16_t FIXED_HALF = FIXED_S16_3_HALF.raw_value;
|
|
return (GRect) {
|
|
// Adding 0.5 to x and y here will ensure we have rounded up when we throw away the fraction
|
|
.origin.x = (center->x.raw_value - (size.w * FIXED_HALF) + FIXED_HALF) >> FIXED_S16_3_PRECISION,
|
|
.origin.y = (center->y.raw_value - (size.h * FIXED_HALF) + FIXED_HALF) >> FIXED_S16_3_PRECISION,
|
|
.size = size,
|
|
};
|
|
}
|
|
|
|
GRect grect_centered_from_polar(GRect rect, GOvalScaleMode scale_mode, int32_t angle, GSize size) {
|
|
const GPointPrecise center = prv_gpointprecise_from_polar(&rect, scale_mode, angle);
|
|
return grect_centered_internal(¢er, size);
|
|
}
|
|
|
|
void grect_polar_calc_values(const GRect *r, GOvalScaleMode scale_mode, GPointPrecise *center,
|
|
Fixed_S16_3 *radius) {
|
|
if (!r) {
|
|
return;
|
|
}
|
|
|
|
GRect rect = *r;
|
|
grect_standardize(&rect);
|
|
|
|
const int16_t FIXED_ONE = FIXED_S16_3_ONE.raw_value;
|
|
const int16_t FIXED_HALF = FIXED_S16_3_HALF.raw_value;
|
|
|
|
if (radius) {
|
|
int16_t side;
|
|
switch (scale_mode) {
|
|
case GOvalScaleModeFitCircle:
|
|
side = grect_shortest_side(rect);
|
|
break;
|
|
case GOvalScaleModeFillCircle:
|
|
side = grect_longest_side(rect);
|
|
break;
|
|
default:
|
|
WTF;
|
|
}
|
|
const int16_t radius_value = ((side + 1) * FIXED_ONE) / 2 - FIXED_ONE;
|
|
*radius = Fixed_S16_3(MAX(0, radius_value));
|
|
}
|
|
if (center) {
|
|
// origin + (origin + len)/2 - 0.5 == (origin * 2 + len) / 2 - 0.5
|
|
const int16_t x = (rect.size.w <= 0) ? rect.origin.x * FIXED_ONE :
|
|
(((2 * rect.origin.x + rect.size.w) * FIXED_ONE) / 2 - FIXED_HALF);
|
|
const int16_t y = (rect.size.h <= 0) ? rect.origin.y * FIXED_ONE :
|
|
(((2 * rect.origin.y + rect.size.h) * FIXED_ONE) / 2 - FIXED_HALF);
|
|
*center = (GPointPrecise) {.x = Fixed_S16_3(x), .y = Fixed_S16_3(y)};
|
|
}
|
|
}
|