pebble/src/fw/applib/graphics/gdraw_command_transforms.c
2025-01-27 11:38:16 -08:00

491 lines
16 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 "gdraw_command_transforms.h"
#include "applib/applib_malloc.auto.h"
#include "applib/graphics/gdraw_command_private.h"
#include "util/trig.h"
#include "applib/ui/animation.h"
#include "applib/ui/animation_interpolate.h"
#include "applib/ui/animation_timing.h"
#include "system/passert.h"
#include "util/math_fixed.h"
#include <stdio.h>
////////////////////
// scale
typedef struct {
GSize from;
GSize to;
} ScaleCBContext;
T_STATIC bool prv_gdraw_command_scale(GDrawCommand *command, uint32_t index, void *context) {
ScaleCBContext *scale = context;
const uint16_t num_points = gdraw_command_get_num_points(command);
for (uint16_t i = 0; i < num_points; i++) {
command->points[i] = gpoint_scale_by_gsize(command->points[i], scale->from, scale->to);
}
return true;
}
void gdraw_command_list_scale(GDrawCommandList *list, GSize from, GSize to) {
ScaleCBContext ctx = {
.from = from,
.to = to,
};
gdraw_command_list_iterate(list, prv_gdraw_command_scale, &ctx);
}
void gdraw_command_image_scale(GDrawCommandImage *image, GSize to) {
gdraw_command_list_scale(&image->command_list, image->size, to);
image->size = to;
}
////////////////////
// attract to square
static int16_t prv_int_attract_to(int16_t value, int16_t bounds, int32_t normalized) {
const int16_t delta_0 = (int16_t) ((0 + 1) - value);
const int16_t delta_b = (int16_t) ((bounds - 1) - value);
const int16_t delta = ABS(delta_0) < ABS(delta_b) ? delta_0 : delta_b;
return (int16_t) (value + delta * normalized / ANIMATION_NORMALIZED_MAX);
}
GPoint gpoint_attract_to_square(GPoint point, GSize size, int32_t normalized) {
// hacky to square - TODO: implement for real
point.y += 1;
point = GPoint(
prv_int_attract_to(point.x, size.w, normalized),
prv_int_attract_to(point.y, size.h, normalized));
return point;
}
typedef struct {
GSize integer_size;
GSize precise_size;
int32_t normalized;
} ToSquareCBContext;
T_STATIC bool prv_gdraw_command_attract_to_square(GDrawCommand *command, uint32_t index,
void *context) {
ToSquareCBContext *to_square = context;
const uint16_t num_points = gdraw_command_get_num_points(command);
for (uint16_t i = 0; i < num_points; i++) {
const GSize size = (command->type == GDrawCommandTypePrecisePath)
? to_square->precise_size : to_square->integer_size;
command->points[i] = gpoint_attract_to_square(command->points[i],
size, to_square->normalized);
}
return true;
}
void gdraw_command_list_attract_to_square(GDrawCommandList *list, GSize size, int32_t normalized) {
ToSquareCBContext ctx = {
.integer_size = size,
.precise_size = gsize_scalar_lshift(size, GPOINT_PRECISE_PRECISION),
.normalized = normalized,
};
gdraw_command_list_iterate(list, prv_gdraw_command_attract_to_square, &ctx);
}
void gdraw_command_image_attract_to_square(GDrawCommandImage *image, int32_t normalized) {
gdraw_command_list_attract_to_square(&image->command_list, image->size, normalized);
}
////////////////////
// gpoint index lookup creator
typedef struct {
const struct {
const GPoint *points;
uint16_t num_points;
} values;
struct {
GPointIndexLookup *lookup;
uint32_t current_index;
} iter;
} GPointCreateIndexCBContext;
T_STATIC bool prv_gdraw_command_create_point_index_lookup(GDrawCommand *command, uint32_t index,
void *context) {
GPointCreateIndexCBContext *lookup = context;
const uint16_t num_points = gdraw_command_get_num_points(command);
for (uint16_t i = 0; i < num_points; i++) {
GPoint point = command->points[i];
if (command->type == GDrawCommandTypePrecisePath) {
point = gpoint_scalar_rshift(point, GPOINT_PRECISE_PRECISION);
}
const uint32_t lookup_length = lookup->values.num_points;
for (uint16_t j = 0; j < lookup_length; j++) {
if (gpoint_equal(&point, &lookup->values.points[j])) {
lookup->iter.lookup->index_lookup[lookup->iter.current_index] = j;
break;
}
}
lookup->iter.current_index++;
}
return true;
}
GPointIndexLookup *gdraw_command_list_create_index_lookup(GDrawCommandList *list,
GPointComparator comparator, void *context, bool reverse) {
uint16_t num_points = 0;
const bool is_precise = false;
GPoint * const points = gdraw_command_list_collect_points(list, is_precise, &num_points);
if (!points) {
return NULL;
}
gpoint_sort(points, num_points, comparator, context, reverse);
GPointIndexLookup *lookup = applib_malloc(sizeof(GPointIndexLookup)
+ num_points * sizeof(uint16_t));
if (!lookup) {
applib_free(points);
return lookup;
}
lookup->num_points = num_points;
lookup->max_index = num_points - 1;
GPointCreateIndexCBContext ctx = {
.values = {
.points = points,
.num_points = num_points,
},
.iter = {
.lookup = lookup,
},
};
gdraw_command_list_iterate(list, prv_gdraw_command_create_point_index_lookup, &ctx);
applib_free(points);
return lookup;
}
typedef struct {
const GPoint origin;
const int32_t angle;
} AngleComparatorContext;
static int prv_angle_comparator(const GPoint * const a, const GPoint * const b,
void *context) {
AngleComparatorContext *ctx = context;
const int16_t angle_a = ABS(positive_modulo(
(atan2_lookup(a->y - ctx->origin.y, a->x - ctx->origin.x) + ctx->angle), TRIG_MAX_ANGLE) -
TRIG_MAX_ANGLE / 2);
const int16_t angle_b = ABS(positive_modulo(
(atan2_lookup(b->y - ctx->origin.y, b->x - ctx->origin.x) + ctx->angle), TRIG_MAX_ANGLE) -
TRIG_MAX_ANGLE / 2);
return (angle_a > angle_b ? 1 : -1);
}
GPointIndexLookup *gdraw_command_list_create_index_lookup_by_angle(GDrawCommandList *list,
GPoint origin, int32_t angle) {
AngleComparatorContext ctx = {
.origin = origin,
.angle = angle,
};
return gdraw_command_list_create_index_lookup(list, prv_angle_comparator, &ctx, false);
}
static int prv_distance_comparator(const GPoint * const a, const GPoint * const b,
void *context) {
const GPoint * const target = context;
uint32_t distance_a = gpoint_distance_squared(*a, *target);
uint32_t distance_b = gpoint_distance_squared(*b, *target);
return (distance_a > distance_b ? 1 : -1);
}
GPointIndexLookup *gdraw_command_list_create_index_lookup_by_distance(GDrawCommandList *list,
GPoint target) {
return gdraw_command_list_create_index_lookup(list, prv_distance_comparator, &target, false);
}
void gpoint_index_lookup_add_at(GPointIndexLookup *lookup, int delay_index, int delay_amount) {
if (delay_index < 0 || delay_index >= lookup->max_index) {
return;
}
// We are adding additional delay, the max delay index increases
lookup->max_index += delay_amount;
for (int i = 0; i < lookup->num_points; i++) {
// The lookup maps definition index => delay index
// We want to add delay to points at or above a certain delay index
if (lookup->index_lookup[i] >= delay_index) {
lookup->index_lookup[i] += delay_amount;
}
}
}
void gpoint_index_lookup_set_groups(GPointIndexLookup *lookup, int num_groups,
Fixed_S32_16 group_delay) {
const int num_points_per_group = lookup->num_points / num_groups;
const int delay_per_group = (num_points_per_group / group_delay.raw_value) /
FIXED_S32_16_ONE.raw_value;
const int group_delay_amount = num_points_per_group + delay_per_group;
for (uint16_t i = num_groups - 1; i >= 1; i--) {
gpoint_index_lookup_add_at(lookup, (i * num_points_per_group), group_delay_amount);
}
}
////////////////////
// segmented scale: index based segmentation of scale + transform
static int16_t prv_int_scale_to(
int16_t value, int16_t size, int16_t from_range, int16_t to_range, int32_t normalized,
InterpolateInt64Function interpolate) {
return value + ((int32_t) value * interpolate(
normalized, from_range - size, to_range - size)) / size;
}
T_STATIC int16_t prv_int_scale_and_translate_to(
int16_t value, int16_t size, int16_t from_range, int16_t to_range,
int16_t from_min, int16_t to_min, int32_t normalized, InterpolateInt64Function interpolate) {
const int32_t scale = prv_int_scale_to(value, size, from_range, to_range, normalized,
interpolate);
const int32_t translate = interpolate(normalized, from_min, to_min);
return scale + translate;
}
GPoint gpoint_scale_to(GPoint point, GSize size, GRect from, GRect to, int32_t normalized,
InterpolateInt64Function interpolate) {
return GPoint(
prv_int_scale_and_translate_to(point.x, size.w, from.size.w, to.size.w,
from.origin.x, to.origin.x, normalized, interpolate),
prv_int_scale_and_translate_to(point.y, size.h, from.size.h, to.size.h,
from.origin.y, to.origin.y, normalized, interpolate));
}
typedef struct {
GRect from;
GRect to;
GSize size;
GPoint offset;
} ScaleToGValues;
typedef struct {
const struct {
ScaleToGValues integer;
ScaleToGValues precise;
Fixed_S32_16 duration_fraction;
GPointIndexLookup *lookup;
AnimationProgress normalized;
InterpolateInt64Function interpolate;
bool is_offset;
} values;
struct {
uint32_t current_index;
} iter;
} ScaleToCBContext;
T_STATIC int64_t prv_default_interpolate(int32_t normalized, int64_t from, int64_t to) {
const int32_t curved = animation_timing_curve(normalized, AnimationCurveEaseInOut);
return interpolate_int64_linear(curved, from, to);
}
T_STATIC bool prv_gdraw_command_scale_segmented(GDrawCommand *command, uint32_t index,
void *context) {
ScaleToCBContext *scale = context;
const ScaleToGValues * const gvalues = (command->type == GDrawCommandTypePrecisePath)
? &scale->values.precise : &scale->values.integer;
const uint16_t num_points = gdraw_command_get_num_points(command);
for (uint16_t i = 0; i < num_points; i++) {
const int32_t point_index = scale->values.lookup->index_lookup[scale->iter.current_index];
GPoint point = command->points[i];
if (scale->values.is_offset) {
gpoint_sub_eq(&point, gvalues->offset);
}
const AnimationProgress normalized = animation_timing_segmented(
scale->values.normalized, point_index, scale->values.lookup->max_index + 1,
scale->values.duration_fraction);
const InterpolateInt64Function interpolate = scale->values.interpolate ?
scale->values.interpolate : prv_default_interpolate;
point = gpoint_scale_to(point, gvalues->size, gvalues->from, gvalues->to, normalized,
interpolate);
if (scale->values.is_offset) {
gpoint_add_eq(&point, gvalues->offset);
}
command->points[i] = point;
scale->iter.current_index++;
}
return true;
}
void gdraw_command_list_scale_segmented_to(
GDrawCommandList *list, GSize size, GRect from, GRect to, AnimationProgress normalized,
InterpolateInt64Function interpolate, GPointIndexLookup *lookup, Fixed_S32_16 duration_fraction,
bool is_offset) {
GPoint offset = GPointZero;
if (is_offset) {
offset = from.origin;
to.origin = gpoint_sub(to.origin, from.origin);
from.origin = GPointZero;
}
ScaleToCBContext ctx = {
.values = {
.integer = {
.from = from,
.to = to,
.size = size,
.offset = offset,
},
.precise = {
.from = grect_scalar_lshift(from, GPOINT_PRECISE_PRECISION),
.to = grect_scalar_lshift(to, GPOINT_PRECISE_PRECISION),
.size = gsize_scalar_lshift(size, GPOINT_PRECISE_PRECISION),
.offset = gpoint_scalar_lshift(offset, GPOINT_PRECISE_PRECISION),
},
.duration_fraction = duration_fraction,
.lookup = lookup,
.normalized = normalized,
.interpolate = interpolate,
.is_offset = is_offset,
},
};
gdraw_command_list_iterate(list, prv_gdraw_command_scale_segmented, &ctx);
}
void gdraw_command_image_scale_segmented_to(
GDrawCommandImage *image, GRect from, GRect to, AnimationProgress normalized,
InterpolateInt64Function interpolate, GPointIndexLookup *lookup, Fixed_S32_16 duration_fraction,
bool is_offset) {
gdraw_command_list_scale_segmented_to(
&image->command_list, image->size, from, to, normalized, interpolate, lookup,
duration_fraction, is_offset);
image->size = to.size;
}
////////////////////
// scale stroke width
typedef struct {
Fixed_S16_3 from;
Fixed_S16_3 to;
AnimationProgress progress;
GStrokeWidthOp from_op;
GStrokeWidthOp to_op;
} ScaleStrokeWidthCBContext;
Fixed_S16_3 prv_stroke_width_transform(Fixed_S16_3 native, Fixed_S16_3 op_value,
GStrokeWidthOp op) {
switch (op) {
case GStrokeWidthOpSet:
return op_value;
case GStrokeWidthOpMultiply:
return Fixed_S16_3_mul(native, op_value);
case GStrokeWidthOpAdd:
return Fixed_S16_3_add(native, op_value);
default:
WTF;
}
}
static bool prv_gdraw_command_scale_stroke_width(GDrawCommand *command, uint32_t index,
void *context) {
ScaleStrokeWidthCBContext *scale = context;
const Fixed_S16_3 stroke_width =
Fixed_S16_3(gdraw_command_get_stroke_width(command) << FIXED_S16_3_PRECISION);
Fixed_S16_3 from_stroke_width = prv_stroke_width_transform(stroke_width, scale->from,
scale->from_op);
Fixed_S16_3 to_stroke_width = prv_stroke_width_transform(stroke_width, scale->to,
scale->to_op);
const uint16_t new_stroke_width = interpolate_int64_linear(
scale->progress, from_stroke_width.raw_value, to_stroke_width.raw_value);
gdraw_command_set_stroke_width(
command, ((new_stroke_width + FIXED_S16_3_HALF.raw_value) >> FIXED_S16_3_PRECISION));
return true;
}
void gdraw_command_list_scale_stroke_width(GDrawCommandList *list, Fixed_S16_3 from, Fixed_S16_3 to,
GStrokeWidthOp from_op, GStrokeWidthOp to_op,
AnimationProgress progress) {
ScaleStrokeWidthCBContext ctx = {
.from = from,
.to = to,
.from_op = from_op,
.to_op = to_op,
.progress = progress,
};
gdraw_command_list_iterate(list, prv_gdraw_command_scale_stroke_width, &ctx);
}
void gdraw_command_image_scale_stroke_width(GDrawCommandImage *image, Fixed_S16_3 from,
Fixed_S16_3 to, GStrokeWidthOp from_op,
GStrokeWidthOp to_op, AnimationProgress progress) {
gdraw_command_list_scale_stroke_width(&image->command_list, from, to, from_op, to_op, progress);
}
////////////////////
// replace color
typedef struct {
GColor from;
GColor to;
} ReplaceColorCBContext;
void gdraw_command_replace_color(GDrawCommand *command, GColor from, GColor to) {
if (gcolor_equal(from, command->fill_color)) {
command->fill_color = to;
}
if (gcolor_equal(from, command->stroke_color)) {
command->stroke_color = to;
}
}
bool prv_replace_color(GDrawCommand *command, uint32_t index, void *context) {
ReplaceColorCBContext *cb_context = context;
gdraw_command_replace_color(command, cb_context->from, cb_context->to);
return true;
}
void gdraw_command_list_replace_color(GDrawCommandList *list, GColor from, GColor to) {
ReplaceColorCBContext context = {
.from = from,
.to = to,
};
gdraw_command_list_iterate(list, prv_replace_color, &context);
}
void gdraw_command_frame_replace_color(GDrawCommandFrame *frame, GColor from, GColor to) {
gdraw_command_list_replace_color(&frame->command_list, from, to);
}