diff --git a/xbanan/Base.cpp b/xbanan/Base.cpp index 0291140..24272b8 100644 --- a/xbanan/Base.cpp +++ b/xbanan/Base.cpp @@ -1,4 +1,5 @@ #include "Definitions.h" +#include "Drawing.h" #include "Extensions.h" #include "Font.h" #include "Image.h" @@ -2410,6 +2411,7 @@ BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet) uint32_t foreground = 0x000000; uint32_t background = 0x000000; + uint16_t line_width = 0; uint32_t font = None; bool graphics_exposures = true; uint32_t clip_mask = 0; @@ -2431,6 +2433,10 @@ BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet) dprintln(" background: {8h}", value); background = value; break; + case 4: + dprintln(" line-width: {}", value); + line_width = value; + break; case 14: dprintln(" font: {}", value); font = value; @@ -2463,6 +2469,7 @@ BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet) .object = Object::GraphicsContext { .foreground = foreground, .background = background, + .line_width = line_width, .font = font, .graphics_exposures = graphics_exposures, .clip_mask = clip_mask, @@ -2504,6 +2511,10 @@ BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet) dprintln(" background: {8h}", value); gc.background = value; break; + case 4: + dprintln(" line-width: {}", value); + gc.line_width = value; + break; case 14: dprintln(" font: {}", value); gc.font = value; @@ -2679,132 +2690,18 @@ BAN::ErrorOr handle_packet(Client& client_info, BAN::ConstByteSpan packet) break; } + case X_PolyLine: + TRY(poly_line(client_info, packet)); + break; + case X_PolySegment: + TRY(poly_segment(client_info, packet)); + break; case X_PolyFillRectangle: - { - auto request = decode(packet).value(); - - dprintln("PolyFillRectangle"); - dprintln(" drawable: {}", request.drawable); - dprintln(" gc: {}", request.gc); - - auto [out_data_u32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, opcode)); - - const auto& gc = TRY_REF(get_gc(client_info, request.gc, opcode)); - const auto foreground = gc.foreground; - - dprintln(" rects:"); - while (!packet.empty()) - { - const auto rect = decode(packet).value(); - - dprintln(" rect"); - dprintln(" x, y: {},{}", rect.x, rect.y); - dprintln(" w, h: {},{}", rect.width, rect.height); - - const int32_t min_x = BAN::Math::max(rect.x, 0); - const int32_t min_y = BAN::Math::max(rect.y, 0); - const int32_t max_x = BAN::Math::min(rect.x + rect.width, out_w); - const int32_t max_y = BAN::Math::min(rect.y + rect.height, out_h); - - for (int32_t y = min_y; y < max_y; y++) - for (int32_t x = min_x; x < max_x; x++) - if (!gc.is_clipped(x, y)) - out_data_u32[y * out_w + x] = foreground; - - if (g_objects[request.drawable]->type == Object::Type::Window) - invalidate_window(request.drawable, min_x, min_y, max_x - min_x, max_y - min_y); - } - + TRY(poly_fill_rectangle(client_info, packet)); break; - } case X_PolyFillArc: - { - auto request = decode(packet).value(); - - dprintln("PolyFillArc"); - dprintln(" drawable: {}", request.drawable); - dprintln(" gc: {}", request.gc); - - auto [out_data_32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, opcode)); - - auto& gc = TRY_REF(get_gc(client_info, request.gc, X_PolyFillArc)); - const auto foreground = gc.foreground; - - const auto normalize_angle = - [](float f) -> float - { - const float radians = f * (2.0f * BAN::numbers::pi_v / 64.0f / 360.0f); - const float mod = BAN::Math::fmod(radians, 2.0f * BAN::numbers::pi_v); - if (mod < 0.0f) - return mod + 2.0f * BAN::numbers::pi_v; - return mod; - }; - - dprintln(" arcs:"); - while (!packet.empty()) - { - const auto arc = decode(packet).value(); - - dprintln(" arc"); - dprintln(" x, y: {},{}", arc.x, arc.y); - dprintln(" w, h: {},{}", arc.width, arc.height); - dprintln(" a1, a2: {},{}", arc.angle1, arc.angle2); - - const int32_t min_x = BAN::Math::max(0, arc.x); - const int32_t min_y = BAN::Math::max(0, arc.y); - - const int32_t max_x = BAN::Math::min(out_w, arc.x + arc.width); - const int32_t max_y = BAN::Math::min(out_h, arc.y + arc.height); - - const auto rx = arc.width / 2; - const auto ry = arc.height / 2; - - const auto cx = arc.x + rx; - const auto cy = arc.y + ry; - - auto angle1 = normalize_angle(arc.angle1); - auto angle2 = normalize_angle(arc.angle1 + arc.angle2); - if (arc.angle2 < 0) - BAN::swap(angle1, angle2); - - const bool full_circle = BAN::Math::abs(arc.angle2) >= 360 * 64; - - for (int32_t y = min_y; y < max_y; y++) - { - for (int32_t x = min_x; x < max_x; x++) - { - const int32_t dx = x - cx; - const int32_t dy = cy - y; - - if ((float)(dx*dx) / (rx*rx) + (float)(dy*dy) / (ry*ry) > 1.0f) - continue; - - if (!full_circle) - { - const auto theta = BAN::Math::atan2(dy, dx); - if (angle1 <= angle2) - { - if (!(theta >= angle1 && theta <= angle2)) - continue; - } - else - { - if (!(theta >= angle1 || theta <= angle2)) - continue; - } - } - - if (!gc.is_clipped(x, y)) - out_data_32[y * out_w + x] = foreground; - } - } - - if (g_objects[request.drawable]->type == Object::Type::Window) - invalidate_window(request.drawable, min_x, min_y, max_x - min_x, max_y - min_y); - } - + TRY(poly_fill_arc(client_info, packet)); break; - } case X_PutImage: { auto request = decode(packet).value(); diff --git a/xbanan/CMakeLists.txt b/xbanan/CMakeLists.txt index 470262f..c084259 100644 --- a/xbanan/CMakeLists.txt +++ b/xbanan/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES main.cpp Base.cpp + Drawing.cpp Extensions.cpp ExtBigReg.cpp ExtGLX.cpp diff --git a/xbanan/Definitions.h b/xbanan/Definitions.h index 0debc1a..5d75182 100644 --- a/xbanan/Definitions.h +++ b/xbanan/Definitions.h @@ -112,6 +112,8 @@ struct Object uint32_t foreground; uint32_t background; + uint16_t line_width; + uint32_t font; bool graphics_exposures; diff --git a/xbanan/Drawing.cpp b/xbanan/Drawing.cpp new file mode 100644 index 0000000..e3f8550 --- /dev/null +++ b/xbanan/Drawing.cpp @@ -0,0 +1,324 @@ +#include "Base.h" +#include "Drawing.h" +#include "SafeGetters.h" +#include "Utils.h" + +#include + +struct DrawSegmentInfo +{ + int32_t x1, y1; + int32_t x2, y2; + + uint32_t* out_data_u32; + uint32_t out_w, out_h; + + int32_t min_x; + int32_t max_x; + int32_t min_y; + int32_t max_y; + + const Object::GraphicsContext& gc; +}; + +static void draw_segment(DrawSegmentInfo& info) +{ + const int32_t width = info.gc.line_width; + + const int32_t min_x = BAN::Math::max(BAN::Math::min(info.x1, info.x2) - width / 2, 0); + const int32_t min_y = BAN::Math::max(BAN::Math::min(info.y1, info.y2) - width / 2, 0); + const int32_t max_x = BAN::Math::min(BAN::Math::max(info.x1, info.x2) + width / 2, info.out_w); + const int32_t max_y = BAN::Math::min(BAN::Math::max(info.y1, info.y2) + width / 2, info.out_h); + + // FIXME: support non solid lines + + if (width == 0) + { + const auto is_in_bounds = + [&info](int32_t x, int32_t y) -> bool + { + return x >= 0 && y >= 0 && x < info.out_w && y < info.out_h; + }; + + int32_t x = info.x1; + int32_t y = info.y1; + + const int32_t dx = +BAN::Math::abs(info.x2 - info.x1); + const int32_t dy = -BAN::Math::abs(info.y2 - info.y1); + const int32_t sx = info.x1 < info.x2 ? 1 : -1; + const int32_t sy = info.y1 < info.y2 ? 1 : -1; + int32_t err = dx + dy; + + for (;;) + { + if (is_in_bounds(x, y) && !info.gc.is_clipped(x, y)) + info.out_data_u32[y * info.out_w + x] = info.gc.foreground; + + const int32_t e2 = 2 * err; + + if (e2 >= dy) + { + if (x == info.x2) + break; + err += dy; + x += sx; + } + + if (e2 <= dx) + { + if (y == info.y2) + break; + err += dx; + y += sy; + } + } + } + else + { + const int32_t dx = info.x2 - info.x1; + const int32_t dy = info.y2 - info.y1; + const int32_t x2y1 = info.x2 * info.y1; + const int32_t y2x1 = info.y2 * info.x1; + const int32_t den2 = dy * dy + dx * dx; + + for (int32_t y = min_y; y <= max_y; y++) + { + for (int32_t x = min_x; x <= max_x; x++) + { + const int32_t num = dy * x - dx * y + x2y1 - y2x1; + if (width * width * den2 < 4 * num * num) + continue; + if (!info.gc.is_clipped(x, y)) + info.out_data_u32[y * info.out_w + x] = info.gc.foreground; + } + } + } + + info.min_x = BAN::Math::min(info.min_x, min_x); + info.min_y = BAN::Math::min(info.min_y, min_y); + info.max_x = BAN::Math::max(info.max_x, max_x); + info.max_y = BAN::Math::max(info.max_y, max_y); +} + +BAN::ErrorOr poly_line(Client& client_info, BAN::ConstByteSpan packet) +{ + auto request = decode(packet).value(); + + dprintln("PolyLine"); + dprintln(" drawable: {}", request.drawable); + dprintln(" gc: {}", request.gc); + + if (packet.size() < sizeof(xPoint)) + return {}; + + auto [out_data_u32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, X_PolyFillRectangle)); + + const auto& gc = TRY_REF(get_gc(client_info, request.gc, X_PolyFillRectangle)); + + DrawSegmentInfo info { + .out_data_u32 = out_data_u32, + .out_w = out_w, + .out_h = out_h, + .min_x = INT32_MAX, + .max_x = INT32_MIN, + .min_y = INT32_MAX, + .max_y = INT32_MIN, + .gc = gc, + }; + + auto prev = decode(packet).value(); + while (packet.size() >= sizeof(xPoint)) + { + auto next = decode(packet).value(); + if (request.coordMode == CoordModePrevious) + { + next.x += prev.x; + next.y += prev.y; + } + + info.x1 = prev.x; + info.y1 = prev.y; + info.x2 = next.x; + info.y2 = next.y; + + draw_segment(info); + + prev = next; + } + + // TODO: should we invalidate bounding box or once per line? + if (g_objects[request.drawable]->type == Object::Type::Window) + invalidate_window(request.drawable, info.min_x, info.min_y, info.max_x - info.min_x + 1, info.max_y - info.min_y + 1); + + return {}; +} + +BAN::ErrorOr poly_segment(Client& client_info, BAN::ConstByteSpan packet) +{ + auto request = decode(packet).value(); + + dprintln("PolySegment"); + dprintln(" drawable: {}", request.drawable); + dprintln(" gc: {}", request.gc); + + auto [out_data_u32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, X_PolyFillRectangle)); + + const auto& gc = TRY_REF(get_gc(client_info, request.gc, X_PolyFillRectangle)); + + DrawSegmentInfo info { + .out_data_u32 = out_data_u32, + .out_w = out_w, + .out_h = out_h, + .min_x = INT32_MAX, + .max_x = INT32_MIN, + .min_y = INT32_MAX, + .max_y = INT32_MIN, + .gc = gc, + }; + + while (packet.size() >= sizeof(xSegment)) + { + auto segment = decode(packet).value(); + + info.x1 = segment.x1; + info.y1 = segment.y1; + info.x2 = segment.x2; + info.y2 = segment.y2; + + draw_segment(info); + } + + // TODO: should we invalidate bounding box or once per line? + if (g_objects[request.drawable]->type == Object::Type::Window) + invalidate_window(request.drawable, info.min_x, info.min_y, info.max_x - info.min_x + 1, info.max_y - info.min_y + 1); + + return {}; +} + +BAN::ErrorOr poly_fill_rectangle(Client& client_info, BAN::ConstByteSpan packet) +{ + auto request = decode(packet).value(); + + dprintln("PolyFillRectangle"); + dprintln(" drawable: {}", request.drawable); + dprintln(" gc: {}", request.gc); + + auto [out_data_u32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, X_PolyFillRectangle)); + + const auto& gc = TRY_REF(get_gc(client_info, request.gc, X_PolyFillRectangle)); + const auto foreground = gc.foreground; + + dprintln(" rects:"); + while (!packet.empty()) + { + const auto rect = decode(packet).value(); + + dprintln(" rect"); + dprintln(" x, y: {},{}", rect.x, rect.y); + dprintln(" w, h: {},{}", rect.width, rect.height); + + const int32_t min_x = BAN::Math::max(rect.x, 0); + const int32_t min_y = BAN::Math::max(rect.y, 0); + const int32_t max_x = BAN::Math::min(rect.x + rect.width, out_w); + const int32_t max_y = BAN::Math::min(rect.y + rect.height, out_h); + + for (int32_t y = min_y; y < max_y; y++) + for (int32_t x = min_x; x < max_x; x++) + if (!gc.is_clipped(x, y)) + out_data_u32[y * out_w + x] = foreground; + + if (g_objects[request.drawable]->type == Object::Type::Window) + invalidate_window(request.drawable, min_x, min_y, max_x - min_x, max_y - min_y); + } + + return {}; +} + +BAN::ErrorOr poly_fill_arc(Client& client_info, BAN::ConstByteSpan packet) +{ + auto request = decode(packet).value(); + + dprintln("PolyFillArc"); + dprintln(" drawable: {}", request.drawable); + dprintln(" gc: {}", request.gc); + + auto [out_data_32, out_w, out_h, _] = TRY(get_drawable_info(client_info, request.drawable, X_PolyFillArc)); + + auto& gc = TRY_REF(get_gc(client_info, request.gc, X_PolyFillArc)); + const auto foreground = gc.foreground; + + const auto normalize_angle = + [](float f) -> float + { + const float radians = f * (2.0f * BAN::numbers::pi_v / 64.0f / 360.0f); + const float mod = BAN::Math::fmod(radians, 2.0f * BAN::numbers::pi_v); + if (mod < 0.0f) + return mod + 2.0f * BAN::numbers::pi_v; + return mod; + }; + + dprintln(" arcs:"); + while (!packet.empty()) + { + const auto arc = decode(packet).value(); + + dprintln(" arc"); + dprintln(" x, y: {},{}", arc.x, arc.y); + dprintln(" w, h: {},{}", arc.width, arc.height); + dprintln(" a1, a2: {},{}", arc.angle1, arc.angle2); + + const int32_t min_x = BAN::Math::max(0, arc.x); + const int32_t min_y = BAN::Math::max(0, arc.y); + + const int32_t max_x = BAN::Math::min(out_w, arc.x + arc.width); + const int32_t max_y = BAN::Math::min(out_h, arc.y + arc.height); + + const auto rx = arc.width / 2; + const auto ry = arc.height / 2; + + const auto cx = arc.x + rx; + const auto cy = arc.y + ry; + + auto angle1 = normalize_angle(arc.angle1); + auto angle2 = normalize_angle(arc.angle1 + arc.angle2); + if (arc.angle2 < 0) + BAN::swap(angle1, angle2); + + const bool full_circle = BAN::Math::abs(arc.angle2) >= 360 * 64; + + for (int32_t y = min_y; y < max_y; y++) + { + for (int32_t x = min_x; x < max_x; x++) + { + const int32_t dx = x - cx; + const int32_t dy = cy - y; + + if ((float)(dx*dx) / (rx*rx) + (float)(dy*dy) / (ry*ry) > 1.0f) + continue; + + if (!full_circle) + { + const auto theta = BAN::Math::atan2(dy, dx); + if (angle1 <= angle2) + { + if (!(theta >= angle1 && theta <= angle2)) + continue; + } + else + { + if (!(theta >= angle1 || theta <= angle2)) + continue; + } + } + + if (!gc.is_clipped(x, y)) + out_data_32[y * out_w + x] = foreground; + } + } + + if (g_objects[request.drawable]->type == Object::Type::Window) + invalidate_window(request.drawable, min_x, min_y, max_x - min_x, max_y - min_y); + } + + return {}; +} diff --git a/xbanan/Drawing.h b/xbanan/Drawing.h new file mode 100644 index 0000000..6a5184b --- /dev/null +++ b/xbanan/Drawing.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Definitions.h" + +BAN::ErrorOr poly_line(Client& client_info, BAN::ConstByteSpan packet); +BAN::ErrorOr poly_segment(Client& client_info, BAN::ConstByteSpan packet); +BAN::ErrorOr poly_fill_rectangle(Client& client_info, BAN::ConstByteSpan packet); +BAN::ErrorOr poly_fill_arc(Client& client_info, BAN::ConstByteSpan packet); diff --git a/xbanan/Font.cpp b/xbanan/Font.cpp index ae3028a..aba7b2d 100644 --- a/xbanan/Font.cpp +++ b/xbanan/Font.cpp @@ -753,10 +753,10 @@ BAN::ErrorOr poly_text(Client& client_info, BAN::ConstByteSpan packet, boo .out_data_u32 = out_data_u32, .out_w = out_w, .out_h = out_h, - .min_x = request.x, - .max_x = request.x, - .min_y = request.y, - .max_y = request.y, + .min_x = INT32_MAX, + .max_x = INT32_MIN, + .min_y = INT32_MAX, + .max_y = INT32_MIN, .font = nullptr, .gc = gc, }; @@ -824,10 +824,10 @@ BAN::ErrorOr image_text(Client& client_info, BAN::ConstByteSpan packet, bo .out_data_u32 = out_data_u32, .out_w = out_w, .out_h = out_h, - .min_x = request.x, - .max_x = request.x, - .min_y = request.y, - .max_y = request.y, + .min_x = INT32_MAX, + .max_x = INT32_MIN, + .min_y = INT32_MAX, + .max_y = INT32_MIN, .font = TRY(get_fontable(client_info, gc.font, opcode)).ptr(), .gc = gc, };