/* * 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.h" #include "bitblt.h" #include "bitblt_private.h" #include "framebuffer.h" #include "graphics_private.h" #include "graphics_private_raw.h" #include "gtransform.h" #include "applib/app_logging.h" #include "applib/applib_malloc.auto.h" #include "kernel/ui/kernel_ui.h" #include "process_management/process_manager.h" #include "process_state/app_state/app_state.h" #include "system/passert.h" #include "system/logging.h" #include "util/bitset.h" #include "util/graphics.h" #include "util/math.h" #include "util/reverse.h" #include "util/trig.h" #include #include #include #if !defined(__clang__) #pragma GCC optimize ("O3") #endif void graphics_draw_pixel(GContext* ctx, GPoint point) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } point.x += ctx->draw_state.drawing_box.origin.x; point.y += ctx->draw_state.drawing_box.origin.y; graphics_private_set_pixel(ctx, point); } T_STATIC void prv_fill_rect_legacy2(GContext *ctx, GRect rect, uint16_t radius, GCornerMask corner_mask, GColor fill_color) { if (gcolor_is_transparent(fill_color)) { fill_color = GColorWhite; } // as this function will only be called with radius 0 to or // to support the legacy2 behavior (where the radius is clamped to 8) it's safe to assume 8px here PBL_ASSERTN(radius <= 8); GBitmap* bitmap = graphics_context_get_bitmap(ctx); // translate to absolute bitmap coordinates: rect.origin.x += ctx->draw_state.drawing_box.origin.x; rect.origin.y += ctx->draw_state.drawing_box.origin.y; // clip it to avoid drawing outside of the bitmap memory: GRect clipped_rect = rect; grect_standardize(&clipped_rect); grect_clip(&clipped_rect, &bitmap->bounds); grect_clip(&clipped_rect, &ctx->draw_state.clip_box); if (grect_is_empty(&clipped_rect)) { return; } // All the row insets are packed into an uint32, taking 4 bits per inset (hence the 8px radius limit): static const uint32_t round_top_corner_lookup[] = { 0x0, 0x01, 0x01, 0x12, 0x113, 0x123, 0x1234, 0x11235, 0x112346, }; static const uint32_t round_bottom_corner_lookup[] = { 0x0, 0x01, 0x10, 0x210, 0x3110, 0x32100, 0x432100, 0x5321100, 0x64321100, }; // Set up the insets for doing the top corners. uint32_t corner_insets_left = (corner_mask & GCornerTopLeft) ? round_top_corner_lookup[radius] : 0; uint32_t corner_insets_right = (corner_mask & GCornerTopRight) ? round_top_corner_lookup[radius] : 0; const unsigned int top_cropped_rows_count = clipped_rect.origin.y - rect.origin.y; const int32_t left_cropped_columns_count = MAX(0, clipped_rect.origin.x - rect.origin.x); const int32_t right_cropped_columns_count = MAX(0, rect.size.w - clipped_rect.size.w - left_cropped_columns_count); if (top_cropped_rows_count) { // Skip over rows for each one that's cropped off the top. corner_insets_left >>= 4 * MIN(top_cropped_rows_count, 8); corner_insets_right >>= 4 * MIN(top_cropped_rows_count, 8); } // Mark the destination dirty before clipped_rect is modified. graphics_context_mark_dirty_rect(ctx, clipped_rect); // bit-block fiddling: const int16_t max_y = clipped_rect.origin.y + clipped_rect.size.h; for (; clipped_rect.origin.y < max_y; ++clipped_rect.origin.y) { if ((clipped_rect.origin.y == (rect.origin.y + rect.size.h) - radius) && (corner_mask & GCornersBottom)) { if (corner_mask & GCornerBottomLeft) { corner_insets_left = round_bottom_corner_lookup[radius]; } if (corner_mask & GCornerBottomRight) { corner_insets_right = round_bottom_corner_lookup[radius]; } } int32_t left_side = MAX((int32_t)(corner_insets_left & 0xf) - left_cropped_columns_count, 0); int32_t right_side = MAX((int32_t)(corner_insets_right & 0xf) - right_cropped_columns_count, 0); int32_t corner_insets = left_side + right_side; int32_t width = corner_insets < clipped_rect.size.w ? (clipped_rect.size.w - corner_insets) : 0; uint32_t x = clipped_rect.origin.x + left_side; corner_insets_left >>= 4; corner_insets_right >>= 4; PBL_ASSERTN(clipped_rect.origin.y < bitmap->bounds.size.h); PBL_ASSERTN(clipped_rect.origin.y >= 0); const uint16_t y = clipped_rect.origin.y; const uint16_t x_end = x + width; graphics_private_draw_horizontal_line_integral(ctx, &ctx->dest_bitmap, y, x, x_end, fill_color); } } //! Return the maximum rounded corner radius allowed for a given rectangle size T_STATIC uint16_t prv_clamp_corner_radius(GSize size, GCornerMask corner_mask, uint16_t radius) { if (corner_mask == GCornerNone) { return 0; } int16_t min_size = MIN(size.w, size.h); if (min_size >= 2 * radius) { return radius; } else { return (min_size / 2); } } typedef void (*FillCircleImplFunc)(GContext *, GPoint pt, uint16_t radius, GCornerMask mask); //! generic fill_rect implementation to avoid code-duplication between aa and non-aa fill_rect void prv_fill_rect_internal(GContext *ctx, const GRect *rect, uint16_t radius, GCornerMask corner_mask, GColor fill_color, uint16_t alt_radius, FillCircleImplFunc circle_func) { // only draw if there is enough to cover the rounded edges - otherwise round down to largest // radius that can be drawn radius = prv_clamp_corner_radius(rect->size, corner_mask, radius); if (radius <= alt_radius) { prv_fill_rect_legacy2(ctx, *rect, radius, corner_mask, fill_color); } else { // These are used to optimize the rectangles that are drawn such that only three rectangles // are drawn always int16_t top_rect_origin_x = rect->origin.x; int16_t top_rect_size_w = rect->size.w; int16_t bottom_rect_origin_x = rect->origin.x; int16_t bottom_rect_size_w = rect->size.w; // Fill 3 rectangles and 4 quadrants if (corner_mask & GCornerTopLeft) { circle_func(ctx, GPoint(rect->origin.x + radius, rect->origin.y + radius), radius, GCornerTopLeft); top_rect_origin_x += radius; top_rect_size_w -= radius; } if (corner_mask & GCornerBottomLeft) { circle_func(ctx, GPoint(rect->origin.x + radius, rect->origin.y + rect->size.h - radius - 1), radius, GCornerBottomLeft); bottom_rect_origin_x += radius; bottom_rect_size_w -= radius; } if (corner_mask & GCornerTopRight) { circle_func(ctx, GPoint(rect->origin.x + rect->size.w - radius - 1, rect->origin.y + radius), radius, GCornerTopRight); top_rect_size_w -= radius; } if (corner_mask & GCornerBottomRight) { circle_func(ctx, GPoint(rect->origin.x + rect->size.w - radius - 1, rect->origin.y + rect->size.h - radius - 1), radius, GCornerBottomRight); bottom_rect_size_w -= radius; } // Top Rect prv_fill_rect_legacy2(ctx, GRect(top_rect_origin_x, rect->origin.y, top_rect_size_w, radius), 0, GCornerNone, fill_color); // Middle Rect prv_fill_rect_legacy2(ctx, GRect(rect->origin.x, rect->origin.y + radius, rect->size.w, rect->size.h - 2 * radius), 0, GCornerNone, fill_color); // Bottom Rect prv_fill_rect_legacy2(ctx, GRect(bottom_rect_origin_x, rect->origin.y + rect->size.h - radius, bottom_rect_size_w, radius), 0, GCornerNone, fill_color); } } T_STATIC void prv_fill_rect_non_aa(GContext* ctx, const GRect *rect, uint16_t radius, GCornerMask corner_mask, GColor fill_color) { // for radii <= 8 we can safely use the legacy2 behavior const uint16_t alt_radius = 8; FillCircleImplFunc circle_func = graphics_circle_quadrant_fill_non_aa; prv_fill_rect_internal(ctx, rect, radius, corner_mask, fill_color, alt_radius, circle_func); } #if PBL_COLOR T_STATIC void prv_fill_rect_aa(GContext* ctx, const GRect *rect, uint16_t radius, GCornerMask corner_mask, GColor fill_color) { FillCircleImplFunc circle_func = graphics_internal_circle_quadrant_fill_aa; prv_fill_rect_internal(ctx, rect, radius, corner_mask, fill_color, 0, circle_func); } #endif // PBL_COLOR void graphics_fill_round_rect(GContext* ctx, const GRect *rect, uint16_t radius, GCornerMask corner_mask) { PBL_ASSERTN(ctx); if (!rect || ctx->lock) { return; } #if PBL_COLOR if (ctx->draw_state.antialiased) { // Antialiased (not supported on 1-bit color) prv_fill_rect_aa(ctx, rect, radius, corner_mask, ctx->draw_state.fill_color); return; } #endif prv_fill_rect_non_aa(ctx, rect, radius, corner_mask, ctx->draw_state.fill_color); } void graphics_fill_round_rect_by_value(GContext* ctx, GRect rect, uint16_t radius, GCornerMask corner_mask) { graphics_fill_round_rect(ctx, &rect, radius, corner_mask); } void graphics_fill_rect(GContext* ctx, const GRect *rect) { graphics_fill_round_rect(ctx, rect, 0, GCornerNone); } T_STATIC void prv_draw_rect(GContext *ctx, const GRect *rect) { GColor fill_color = ctx->draw_state.fill_color; ctx->draw_state.fill_color = ctx->draw_state.stroke_color; graphics_fill_rect(ctx, &GRect(rect->origin.x, rect->origin.y, rect->size.w, 1)); // top graphics_fill_rect(ctx, &GRect(rect->origin.x, rect->origin.y + rect->size.h - 1, rect->size.w, 1)); // bottom graphics_fill_rect(ctx, &GRect(rect->origin.x, rect->origin.y + 1, 1, rect->size.h - 2)); // left graphics_fill_rect(ctx, &GRect(rect->origin.x + rect->size.w - 1, rect->origin.y + 1, 1, rect->size.h - 2)); // right ctx->draw_state.fill_color = fill_color; } #if PBL_COLOR T_STATIC void prv_draw_rect_aa_stroked(GContext *ctx, const GRect *rect, uint8_t stroke_width) { const GPoint tl = GPoint(rect->origin.x, rect->origin.y); const GPoint tr = GPoint(rect->origin.x + rect->size.w - 1, rect->origin.y); const GPoint bl = GPoint(rect->origin.x, rect->origin.y + rect->size.h - 1); const GPoint br = GPoint(rect->origin.x + rect->size.w - 1, rect->origin.y + rect->size.h - 1); graphics_line_draw_stroked_aa(ctx, tl, tr, stroke_width); graphics_line_draw_stroked_aa(ctx, tl, bl, stroke_width); graphics_line_draw_stroked_aa(ctx, tr, br, stroke_width); graphics_line_draw_stroked_aa(ctx, bl, br, stroke_width); } #endif // PBL_COLOR T_STATIC void prv_draw_rect_stroked(GContext *ctx, const GRect *rect, uint8_t stroke_width) { const GPoint tl = GPoint(rect->origin.x, rect->origin.y); const GPoint tr = GPoint(rect->origin.x + rect->size.w - 1, rect->origin.y); const GPoint bl = GPoint(rect->origin.x, rect->origin.y + rect->size.h - 1); const GPoint br = GPoint(rect->origin.x + rect->size.w - 1, rect->origin.y + rect->size.h - 1); graphics_line_draw_stroked_non_aa(ctx, tl, tr, stroke_width); graphics_line_draw_stroked_non_aa(ctx, tl, bl, stroke_width); graphics_line_draw_stroked_non_aa(ctx, tr, br, stroke_width); graphics_line_draw_stroked_non_aa(ctx, bl, br, stroke_width); } void graphics_draw_rect(GContext* ctx, const GRect *rect) { PBL_ASSERTN(ctx); if (!rect || ctx->lock) { return; } if (ctx->draw_state.stroke_width <= 2) { // Note: stroke width == 2 is rounded down to stroke width of 1 prv_draw_rect(ctx, rect); return; } #if PBL_COLOR if (ctx->draw_state.antialiased) { // Antialiased and Stroke Width > 2 prv_draw_rect_aa_stroked(ctx, rect, ctx->draw_state.stroke_width); return; } #endif // Non-Antialiased and Stroke Width > 2 // Note: stroke width must be odd and greater than 2 prv_draw_rect_stroked(ctx, rect, ctx->draw_state.stroke_width); } void graphics_draw_rect_by_value(GContext* ctx, GRect rect) { graphics_draw_rect(ctx, &rect); } void graphics_draw_rect_precise(GContext* ctx, const GRectPrecise *rect) { const Fixed_S16_3 right = grect_precise_get_max_x(rect); const Fixed_S16_3 bottom = grect_precise_get_max_y(rect); const GPointPrecise top_left = rect->origin; const GPointPrecise top_right = {right, rect->origin.y}; const GPointPrecise bottom_right = {right, bottom}; const GPointPrecise bottom_left = {rect->origin.x, bottom}; graphics_line_draw_precise_stroked(ctx, top_left, top_right); graphics_line_draw_precise_stroked(ctx, top_right, bottom_right); graphics_line_draw_precise_stroked(ctx, bottom_right, bottom_left); graphics_line_draw_precise_stroked(ctx, bottom_left, top_left); } // This takes care of all routines since it re-uses existing AA and SW functionality in draw line // and draw circle T_STATIC void prv_draw_round_rect(GContext* ctx, const GRect *rect, uint16_t radius) { const GPoint origin = rect->origin; const int16_t width = rect->size.w; const int16_t height = rect->size.h; // Subtract out twice the respective radius values to get the actual width and height of the // rectangle lines const int16_t width_actual = width - (2 * radius); const int16_t height_actual = height - (2 * radius); // Take into account the radius values to determine the eight points for each of the four lines const GPoint top_l = GPoint(origin.x + radius, origin.y); const GPoint top_r = GPoint(origin.x + radius + width_actual - 1, origin.y); const GPoint bottom_l = GPoint(origin.x + radius, origin.y + height - 1); const GPoint bottom_r = GPoint(origin.x + radius + width_actual - 1, origin.y + height - 1); const GPoint left_t = GPoint(origin.x, origin.y + radius); const GPoint left_b = GPoint(origin.x, origin.y + radius + height_actual - 1); const GPoint right_t = GPoint(origin.x + width - 1, origin.y + radius); const GPoint right_b = GPoint(origin.x + width - 1, origin.y + radius + height_actual - 1); // Draw lines between each transformed corner point graphics_draw_line(ctx, top_l, top_r); // top graphics_draw_line(ctx, bottom_l, bottom_r); // bottom graphics_draw_line(ctx, left_t, left_b); // left graphics_draw_line(ctx, right_t, right_b); // right // Draw quadrants const GPoint tl = GPoint(origin.x + radius, origin.y + radius); const GPoint tr = gpoint_add(tl, GPoint(width_actual - 1, 0)); const GPoint bl = gpoint_add(tl, GPoint(0, height_actual - 1)); const GPoint br = gpoint_add(tl, GPoint(width_actual - 1, height_actual - 1)); graphics_circle_quadrant_draw(ctx, tl, radius, GCornerTopLeft); graphics_circle_quadrant_draw(ctx, bl, radius, GCornerBottomLeft); graphics_circle_quadrant_draw(ctx, tr, radius, GCornerTopRight); graphics_circle_quadrant_draw(ctx, br, radius, GCornerBottomRight); } #if PBL_COLOR T_STATIC void prv_draw_round_rect_aa(GContext* ctx, const GRect *rect, uint16_t radius) { // Assumes AA and stroke_width is set appropriately in ctx prv_draw_round_rect(ctx, rect, radius); } T_STATIC void prv_draw_round_rect_aa_stroked(GContext* ctx, const GRect *rect, uint16_t radius, uint8_t stroke_width) { // Assumes AA and stroke_width is set appropriately in ctx prv_draw_round_rect(ctx, rect, radius); } #endif // SCREEN_COLOR_DEPTH_BITS T_STATIC void prv_draw_round_rect_stroked(GContext* ctx, const GRect *rect, uint16_t radius, uint8_t stroke_width) { // Assumes AA and stroke_width is set appropriately in ctx prv_draw_round_rect(ctx, rect, radius); } static void prv_graphics_convert_8_bit_to_1_bit(const GBitmap *from, GBitmap *to) { const GRect bounds = from->bounds; uint8_t *to_buffer = (uint8_t *) to->addr; const int y_start = bounds.origin.y; const int y_end = y_start + bounds.size.h; const int x_start = bounds.origin.x; const int x_end = x_start + bounds.size.w; for (int y = y_start; y < y_end; ++y) { int to_idx_base = y * to->row_size_bytes; uint8_t *line = to_buffer + to_idx_base; for (int x = x_start; x < x_end; ++x) { bitset8_clear(line, x); } } } void graphics_draw_round_rect(GContext* ctx, const GRect *rect, uint16_t radius) { PBL_ASSERTN(ctx); if (!rect || ctx->lock) { return; } // only draw if there is enough to cover the rounded edges - otherwise round down to largest // radius that can be drawn radius = prv_clamp_corner_radius(rect->size, GCornersAll, radius); if (radius == 0) { graphics_draw_rect(ctx, rect); } else { #if PBL_COLOR if (ctx->draw_state.antialiased) { if (ctx->draw_state.stroke_width > 1) { // Antialiased and Stroke Width > 1 // Note: stroke width == 2 is rounded down to stroke width of 1 prv_draw_round_rect_aa_stroked(ctx, rect, radius, ctx->draw_state.stroke_width); return; } else { // Antialiased and Stroke Width == 1 (not supported on 1-bit color) // Note: stroke width == 2 is rounded down to stroke width of 1 prv_draw_round_rect_aa(ctx, rect, radius); return; } } #endif if (ctx->draw_state.stroke_width > 1) { // Non-Antialiased and Stroke Width > 1 prv_draw_round_rect_stroked(ctx, rect, radius, ctx->draw_state.stroke_width); } else { // Non-Antialiased and Stroke Width == 1 prv_draw_round_rect(ctx, rect, radius); } } } void graphics_draw_round_rect_by_value(GContext* ctx, GRect rect, uint16_t radius) { graphics_draw_round_rect(ctx, &rect, radius); } void graphics_context_init(GContext *context, FrameBuffer *framebuffer, GContextInitializationMode init_mode) { PBL_ASSERTN(context); PBL_ASSERTN(framebuffer); *context = (GContext) { // For apps, this is run before the app has a chance to run, so there's no concern here of the // app changing its framebuffer size. .dest_bitmap = framebuffer_get_as_bitmap(framebuffer, &framebuffer->size), .parent_framebuffer = framebuffer, .parent_framebuffer_vertical_offset = 0, .lock = false }; // init the font cache FontCache *font_cache = &context->font_cache; memset(font_cache->cache_keys, 0, sizeof(font_cache->cache_keys)); memset(font_cache->cache_data, 0, sizeof(font_cache->cache_data)); keyed_circular_cache_init(&font_cache->line_cache, font_cache->cache_keys, font_cache->cache_data, sizeof(LineCacheData), LINE_CACHE_SIZE); graphics_context_set_default_drawing_state(context, init_mode); } void graphics_context_set_default_drawing_state(GContext *ctx, GContextInitializationMode init_mode) { PBL_ASSERTN(ctx); GBitmap* bitmap = graphics_context_get_bitmap(ctx); ctx->draw_state = (GDrawState) { .stroke_color = GColorBlack, .fill_color = GColorBlack, .text_color = GColorWhite, .tint_color = GColorWhite, .compositing_mode = GCompOpAssign, .clip_box = bitmap->bounds, .drawing_box = bitmap->bounds, #if PBL_COLOR .antialiased = !process_manager_compiled_with_legacy2_sdk(), #endif .stroke_width = 1, .draw_implementation = &g_default_draw_implementation, .avoid_text_orphans = (init_mode == GContextInitializationMode_System), }; } GDrawState graphics_context_get_drawing_state(GContext* ctx) { PBL_ASSERTN(ctx); return ctx->draw_state; } void graphics_context_set_drawing_state(GContext* ctx, GDrawState draw_state) { PBL_ASSERTN(ctx); ctx->draw_state = draw_state; } void graphics_context_move_draw_box(GContext* ctx, GPoint offset) { PBL_ASSERTN(ctx); ctx->draw_state.drawing_box.origin = gpoint_add(ctx->draw_state.drawing_box.origin, offset); } void graphics_context_set_stroke_color(GContext* ctx, GColor color) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } #if PBL_BW color = gcolor_get_bw(color); #else color = gcolor_closest_opaque(color); #endif ctx->draw_state.stroke_color = color; } void graphics_context_set_stroke_color_2bit(GContext* ctx, GColor2 color) { graphics_context_set_stroke_color(ctx, get_native_color(color)); } void graphics_context_set_fill_color(GContext* ctx, GColor color) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } #if PBL_BW color = gcolor_get_grayscale(color); #else color = gcolor_closest_opaque(color); #endif ctx->draw_state.fill_color = color; } void graphics_context_set_fill_color_2bit(GContext* ctx, GColor2 color) { graphics_context_set_fill_color(ctx, get_native_color(color)); } void graphics_context_set_text_color(GContext* ctx, GColor color) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } #if PBL_BW color = gcolor_get_bw(color); #else color = gcolor_closest_opaque(color); #endif ctx->draw_state.text_color = color; } void graphics_context_set_text_color_2bit(GContext* ctx, GColor2 color) { graphics_context_set_text_color(ctx, get_native_color(color)); } void graphics_context_set_tint_color(GContext *ctx, GColor color) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } ctx->draw_state.tint_color = gcolor_closest_opaque(color); } void graphics_context_set_compositing_mode(GContext* ctx, GCompOp mode) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } ctx->draw_state.compositing_mode = mode; } void graphics_context_set_antialiased(GContext* ctx, bool enable) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } #if PBL_COLOR ctx->draw_state.antialiased = enable; #endif } bool graphics_context_get_antialiased(GContext *ctx) { PBL_ASSERTN(ctx); return PBL_IF_COLOR_ELSE(ctx->draw_state.antialiased, false); } void graphics_context_set_stroke_width(GContext* ctx, uint8_t stroke_width) { PBL_ASSERTN(ctx); if (ctx->lock) { return; } // Ignore if stroke width == 0 if (stroke_width >= 1) { ctx->draw_state.stroke_width = stroke_width; } } GSize graphics_context_get_framebuffer_size(GContext *ctx) { if (ctx && ctx->parent_framebuffer) { return ctx->parent_framebuffer->size; } else { return GSize(DISP_COLS, DISP_ROWS); } } GBitmap* graphics_context_get_bitmap(GContext* ctx) { PBL_ASSERTN(ctx); return &ctx->dest_bitmap; } void graphics_context_mark_dirty_rect(GContext* ctx, GRect rect) { PBL_ASSERTN(ctx); if (ctx->parent_framebuffer) { framebuffer_mark_dirty_rect(ctx->parent_framebuffer, rect); } } bool graphics_frame_buffer_is_captured(GContext* ctx) { PBL_ASSERTN(ctx); return ctx->lock; } GBitmap* graphics_capture_frame_buffer_format(GContext *ctx, GBitmapFormat format) { PBL_ASSERTN(ctx); if (ctx->lock) { APP_LOG(APP_LOG_LEVEL_WARNING, "Frame buffer has already been captured; it cannot be captured again until " "graphics_release_frame_buffer has been called."); return NULL; } ctx->lock = true; GBitmap *native = graphics_context_get_bitmap(ctx); if (format == native->info.format) { return native; } GBitmap *result = NULL; if (format == GBitmapFormat1Bit && native->info.format == GBitmapFormat8Bit) { // Create a new blank gbitmap in the correct format. const GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx); if (process_manager_compiled_with_legacy2_sdk()) { result = app_state_legacy2_get_2bit_framebuffer(); } else { result = gbitmap_create_blank(native_framebuffer->bounds.size, GBitmapFormat1Bit); } if (result) { prv_graphics_convert_8_bit_to_1_bit(native_framebuffer, result); } } if (!result) { ctx->lock = false; } return result; } GBitmap* graphics_capture_frame_buffer_2bit(GContext *ctx) { return graphics_capture_frame_buffer_format(ctx, GBitmapFormat1Bit); } MOCKABLE GBitmap *graphics_capture_frame_buffer(GContext *ctx) { PBL_ASSERTN(ctx); return graphics_capture_frame_buffer_format(ctx, GBITMAP_NATIVE_FORMAT); } #include "system/profiler.h" MOCKABLE bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) { PBL_ASSERTN(ctx); GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx); if (gbitmap_get_format(buffer) != GBITMAP_NATIVE_FORMAT) { ctx->lock = false; bitblt_bitmap_into_bitmap(native_framebuffer, buffer, GPointZero, GCompOpAssign, GColorWhite); framebuffer_dirty_all(ctx->parent_framebuffer); // Don't destroy the bitmap we got from app_state_legacy2_get_2bit_framebuffer() if (!process_manager_compiled_with_legacy2_sdk()) { gbitmap_destroy(buffer); } return true; } if (buffer == native_framebuffer) { ctx->lock = false; framebuffer_dirty_all(ctx->parent_framebuffer); return true; } return false; }