glm::mat4 ToGLM(const WindowProjection &projection, const GeoPoint &reference) { fixed angle = projection.GetScreenAngle().Degrees(); fixed scale = projection.GetScale(); const RasterPoint &screen_origin = projection.GetScreenOrigin(); const GeoPoint &screen_location = projection.GetGeoLocation(); const GeoPoint projection_delta = reference - screen_location; const fixed scale_r = scale * FAISphere::REARTH; const fixed scale_x = scale_r * screen_location.latitude.fastcosine(); const fixed scale_y = -scale_r; const glm::vec3 scale_vec(GLfloat(scale_x), GLfloat(scale_y), 1); glm::mat4 matrix = glm::scale(glm::rotate(glm::translate(glm::mat4(), glm::vec3(screen_origin.x, screen_origin.y, 0)), GLfloat(angle), glm::vec3(0, 0, -1)), scale_vec); matrix = glm::translate(matrix, glm::vec3(GLfloat(projection_delta.longitude.Native()), GLfloat(projection_delta.latitude.Native()), 0.)); return matrix; }
/** * Draws the terrain to the given canvas * @param canvas The drawing canvas * @param map_projection The Projection * @param sunazimuth Azimuth of the sun (for terrain shading) */ void TerrainRenderer::Draw(Canvas &canvas, const WindowProjection &map_projection, const Angle sunazimuth) { const bool do_water = true; const unsigned height_scale = 4; const int interp_levels = 2; const bool is_terrain = true; const bool do_shading = is_terrain && SlopeShading; const COLORRAMP *const color_ramp = &terrain_colors[TerrainRamp][0]; if (color_ramp != last_color_ramp) { raster_renderer.ColorTable(color_ramp, do_water, height_scale, interp_levels); last_color_ramp = color_ramp; } { RasterTerrain::Lease map(*terrain); raster_renderer.ScanMap(map, map_projection); } raster_renderer.GenerateImage(is_terrain, do_shading, height_scale, TerrainContrast, TerrainBrightness, sunazimuth); CopyTo(canvas, map_projection.GetScreenWidth(), map_projection.GetScreenHeight()); }
void RasterRenderer::ScanMap(const RasterMap &map, const WindowProjection &projection) { // Coordinates of the MapWindow center unsigned x = projection.GetScreenWidth() / 2; unsigned y = projection.GetScreenHeight() / 2; // GeoPoint corresponding to the MapWindow center GeoPoint Gmid = projection.ScreenToGeo(x, y); // GeoPoint "next to" Gmid (depends on terrain resolution) GeoPoint Gneighbor = projection.ScreenToGeo(x + quantisation_pixels, y + quantisation_pixels); // Geographical edge length of pixel in the MapWindow center in meters pixel_size = fixed_sqrt_half * Gmid.Distance(Gneighbor); // set resolution fixed map_pixel_size = map.pixel_distance(Gmid, 1); fixed q = map_pixel_size / pixel_size; if (pixel_size < fixed(3000)) { /* round down to reduce slope shading artefacts (caused by RasterBuffer interpolation) */ quantisation_effective = std::max(1, (int)q); if (quantisation_effective > 25) /* disable slope shading when zoomed in very near (not enough terrain resolution to make a useful slope calculation) */ quantisation_effective = 0; } else /* disable slope shading when zoomed out very far (too tiny) */ quantisation_effective = 0; height_matrix.Fill(map, projection, quantisation_pixels, true); }
void ApplyProjection(const WindowProjection &projection, const GeoPoint &reference) { fixed angle = projection.GetScreenAngle().Degrees(); fixed scale = projection.GetScale(); const RasterPoint &screen_origin = projection.GetScreenOrigin(); const GeoPoint &screen_location = projection.GetGeoLocation(); const GeoPoint projection_delta = reference - screen_location; const fixed scale_r = scale * FAISphere::REARTH; const fixed scale_x = scale_r * screen_location.latitude.fastcosine(); const fixed scale_y = -scale_r; #ifdef HAVE_GLES #ifdef FIXED_MATH GLfixed fixed_angle = angle.as_glfixed(); #else GLfixed fixed_angle = angle * (1<<16); #endif glTranslatex((int)screen_origin.x << 16, (int)screen_origin.y << 16, 0); glRotatex(fixed_angle, 0, 0, -(1<<16)); #else glTranslatef(screen_origin.x, screen_origin.y, 0.); glRotatef((GLfloat)angle, 0., 0., -1.); #endif glScalef(GLfloat(scale_x), GLfloat(scale_y), 1.); glTranslatef(GLfloat(projection_delta.longitude.Native()), GLfloat(projection_delta.latitude.Native()), 0.); }
inline bool AirspaceRenderer::DrawFill(Canvas &buffer_canvas, Canvas &stencil_canvas, const WindowProjection &projection, const AirspaceRendererSettings &settings, const AirspaceWarningCopy &awc, const AirspacePredicate &visible) { StencilMapCanvas helper(buffer_canvas, stencil_canvas, projection, settings); AirspaceVisitorMap v(helper, awc, settings, look); // JMW TODO wasteful to draw twice, can't it be drawn once? // we are using two draws so borders go on top of everything const auto range = airspaces->QueryWithinRange(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters()); for (const auto &i : range) { const AbstractAirspace &airspace = i.GetAirspace(); if (visible(airspace)) v.Visit(airspace); } return v.Commit(); }
void TransparentRendererCache::AlphaBlendTo(Canvas &canvas, const WindowProjection &projection, uint8_t alpha) const { assert(canvas.IsDefined()); assert(buffer.IsDefined()); assert(projection.IsValid()); assert(compare_projection.IsDefined()); assert(Check(projection)); if (empty) return; const unsigned width = projection.GetScreenWidth(), height = projection.GetScreenHeight(); #ifdef USE_MEMORY_CANVAS canvas.AlphaBlendNotWhite(0, 0, width, height, buffer, 0, 0, width, height, alpha); #else canvas.AlphaBlend(0, 0, width, height, buffer, 0, 0, width, height, alpha); #endif }
void TopographyThread::Trigger(const WindowProjection &_projection) { assert(_projection.IsValid()); const GeoBounds new_bounds = _projection.GetScreenBounds(); if (last_bounds.IsValid() && last_bounds.IsInside(new_bounds)) { /* still inside cache bounds - now check if we crossed a scale threshold for at least one file, which would mean we have to update a file which was not updated for the current cache bounds */ if (scale_threshold < 0 || _projection.GetMapScale() >= scale_threshold) /* the cache is still fresh */ return; } last_bounds = new_bounds.Scale(1.1); scale_threshold = store.GetNextScaleThreshold(_projection.GetMapScale()); { const ScopeLock protect(mutex); next_projection = _projection; StandbyThread::Trigger(); } }
void TopographyFileRenderer::UpdateVisibleShapes(const WindowProjection &projection) { if (file.GetSerial() == visible_serial && visible_bounds.IsInside(projection.GetScreenBounds()) && projection.GetScreenBounds().Scale(fixed_two).IsInside(visible_bounds)) /* cache is clean */ return; visible_serial = file.GetSerial(); visible_bounds = projection.GetScreenBounds().Scale(fixed(1.2)); visible_shapes.clear(); visible_labels.clear(); for (auto it = file.begin(), end = file.end(); it != end; ++it) { const XShape &shape = *it; if (!visible_bounds.Overlaps(shape.get_bounds())) continue; if (shape.get_type() != MS_SHAPE_NULL) visible_shapes.push_back(&shape); if (shape.get_label() != NULL) visible_labels.push_back(&shape); } }
CompareProjection::CompareProjection(const WindowProjection &projection) :corners(projection), latitude_cos(corners.top_left.latitude.fastcosine()), max_delta(SimpleSquareDistance(corners.top_left, corners.top_right, latitude_cos) / (projection.GetScreenWidth() * projection.GetScreenWidth())) { }
bool TopographyFile::Update(const WindowProjection &map_projection) { if (IsEmpty()) return false; if (map_projection.GetMapScale() > scale_threshold) /* not visible, don't update cache now */ return false; const GeoBounds screenRect = map_projection.GetScreenBounds(); if (cache_bounds.IsValid() && cache_bounds.IsInside(screenRect)) /* the cache is still fresh */ return false; cache_bounds = map_projection.GetScreenBounds().Scale(fixed(2)); rectObj deg_bounds = ConvertRect(cache_bounds); // Test which shapes are inside the given bounds and save the // status to file.status msShapefileWhichShapes(&file, dir, deg_bounds, 0); // If not a single shape is inside the bounds if (!file.status) { // ... clear the whole buffer ClearCache(); return false; } // Iterate through the shapefile entries const ShapeList **current = &first; auto it = shapes.begin(); for (int i = 0; i < file.numshapes; ++i, ++it) { if (!msGetBit(file.status, i)) { // If the shape is outside the bounds // delete the shape from the cache delete it->shape; it->shape = NULL; } else { // is inside the bounds if (it->shape == NULL) // shape isn't cached yet -> cache the shape it->shape = new XShape(&file, i, label_field); // update list pointer *current = it; current = &it->next; } } // end of list marker *current = NULL; ++serial; return true; }
bool TransparentRendererCache::Check(const WindowProjection &projection) const { assert(projection.IsValid()); return buffer.IsDefined() && buffer.GetWidth() == projection.GetScreenWidth() && buffer.GetHeight() == projection.GetScreenHeight() && compare_projection.Compare(projection); }
void MapOverlayBitmap::Draw(Canvas &canvas, const WindowProjection &projection) noexcept { if (!simple_bounds.Overlaps(projection.GetScreenBounds())) /* not visible, outside of screen area */ return; auto clipped = Clip(bounds, projection.GetScreenBounds()); if (clipped.empty()) return; GLTexture &texture = *bitmap.GetNative(); const PixelSize allocated = texture.GetAllocatedSize(); const double x_factor = double(texture.GetWidth()) / allocated.cx; const double y_factor = double(texture.GetHeight()) / allocated.cy; Point2D<GLfloat> coord[16]; BulkPixelPoint vertices[16]; const ScopeVertexPointer vp(vertices); texture.Bind(); const ScopeTextureConstantAlpha blend(use_bitmap_alpha, alpha); glEnableVertexAttribArray(OpenGL::Attribute::TEXCOORD); glVertexAttribPointer(OpenGL::Attribute::TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, coord); for (const auto &polygon : clipped) { const auto &ring = polygon.outer(); size_t n = ring.size(); if (ring.front() == ring.back()) --n; for (size_t i = 0; i < n; ++i) { const auto v = GeoFrom2D(ring[i]); auto p = MapInQuadrilateral(bounds, v); coord[i].x = p.x * x_factor; coord[i].y = p.y * y_factor; if (bitmap.IsFlipped()) coord[i].y = 1 - coord[i].y; vertices[i] = projection.GeoToScreen(v); } glDrawArrays(GL_TRIANGLE_FAN, 0, n); } glDisableVertexAttribArray(OpenGL::Attribute::TEXCOORD); }
inline void AirspaceRenderer::DrawOutline(Canvas &canvas, const WindowProjection &projection, const AirspaceRendererSettings &settings, const AirspacePredicate &visible) const { AirspaceOutlineRenderer outline_renderer(canvas, projection, look, settings); airspaces->VisitWithinRange(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters(), outline_renderer, visible); }
void TransparentRendererCache::CopyAndTo(Canvas &canvas, const WindowProjection &projection) const { if (empty) return; canvas.CopyAnd(0, 0, projection.GetScreenWidth(), projection.GetScreenHeight(), buffer, 0, 0); }
void BackgroundDrawHelper::sun_from_wind(const WindowProjection& projection, const SpeedVector& wind) { // draw sun from constant angle if very low wind speed if (wind.norm < fixed_half) { m_sun_azimuth = projection.GetScreenAngle() - Angle::degrees(fixed(45.0)); } else { m_sun_azimuth = projection.GetScreenAngle() + wind.bearing; } }
bool TrailRenderer::LoadTrace(const TraceComputer &trace_computer, unsigned min_time, const WindowProjection &projection) { trace.clear(); trace_computer.LockedCopyTo(trace, min_time, projection.GetGeoScreenCenter(), projection.DistancePixelsToMeters(3)); return !trace.empty(); }
static void GetPolygonPoints(std::vector<RasterPoint> &pts, const AirspacePolygon &airspace, const RasterPoint pt, unsigned radius) { GeoBounds bounds = airspace.GetGeoBounds(); GeoPoint center = bounds.GetCenter(); fixed geo_heigth = bounds.GetGeoHeight(); fixed geo_width = bounds.GetGeoWidth(); fixed geo_size = std::max(geo_heigth, geo_width); WindowProjection projection; projection.SetScreenSize({radius * 2, radius * 2}); projection.SetScreenOrigin(pt.x, pt.y); projection.SetGeoLocation(center); projection.SetScale(fixed(radius * 2) / geo_size); projection.SetScreenAngle(Angle::Zero()); projection.UpdateScreenBounds(); const SearchPointVector &border = airspace.GetPoints(); pts.reserve(border.size()); for (auto it = border.begin(), it_end = border.end(); it != it_end; ++it) pts.push_back(projection.GeoToScreen(it->GetLocation())); }
virtual void OnPaint(Canvas &canvas) override { canvas.ClearWhite(); const GeoPoint a(Angle::Degrees(7.70722), Angle::Degrees(51.052)); const GeoPoint b(Angle::Degrees(11.5228), Angle::Degrees(50.3972)); WindowProjection projection; projection.SetScreenOrigin(canvas.GetWidth() / 2, canvas.GetHeight() / 2); projection.SetGeoLocation(a.Middle(b)); projection.SetScreenSize(canvas.GetSize()); projection.SetScaleFromRadius(fixed(400000)); projection.UpdateScreenBounds(); canvas.SelectBlackPen(); canvas.SelectHollowBrush(); RasterPoint pa = projection.GeoToScreen(a); canvas.DrawCircle(pa.x, pa.y, 4); RasterPoint pb = projection.GeoToScreen(b); canvas.DrawCircle(pb.x, pb.y, 4); RenderFAISector(canvas, projection, a, b, false, settings); }
void HeightMatrix::Fill(const RasterMap &map, const WindowProjection &projection, unsigned quantisation_pixels, bool interpolate) { const unsigned screen_width = projection.GetScreenWidth(); const unsigned screen_height = projection.GetScreenHeight(); SetSize((screen_width + quantisation_pixels - 1) / quantisation_pixels, (screen_height + quantisation_pixels - 1) / quantisation_pixels); minimum = 0x7fff; maximum = 0; for (unsigned y = 0; y < screen_height; y += quantisation_pixels) { const FastRowRotation rotation = projection.GetScreenAngleRotation(y - projection.GetScreenOrigin().y); short *p = data.begin() + y * width / quantisation_pixels; for (unsigned x = 0; x < screen_width; x += quantisation_pixels) { #ifndef SLOW_TERRAIN_STUFF const FastRowRotation::Pair r = rotation.Rotate(x - projection.GetScreenOrigin().x); GeoPoint gp; gp.Latitude = projection.GetGeoLocation().Latitude - projection.PixelsToAngle(r.second); gp.Longitude = projection.GetGeoLocation().Longitude + projection.PixelsToAngle(r.first) * gp.Latitude.invfastcosine(); #else GeoPoint gp = projection.ScreenToGeo(x, y); #endif short h = interpolate ? map.GetFieldInterpolated(gp) : map.GetField(gp); if (!RasterBuffer::is_special(h)) { if (h < minimum) minimum = h; if (h > maximum) maximum = h; } *p++ = h; } assert(p <= data.end()); } }
void TrackLineRenderer::Draw(Canvas &canvas, const WindowProjection &projection, const RasterPoint pos, const NMEAInfo &basic, const DerivedInfo &calculated, const MapSettings &settings, bool wind_relative) { if (!basic.track_available || !basic.attitude.IsHeadingUseable()) return; if (basic.airspeed_available.IsValid() && (calculated.turn_rate_heading_smoothed.Absolute()>= MIN_RATE)) { TrackLineRenderer::DrawProjected(canvas, projection, basic, calculated, settings, wind_relative); } if (settings.display_ground_track == DisplayGroundTrack::OFF || calculated.circling) return; if (settings.display_ground_track == DisplayGroundTrack::AUTO && (basic.track - basic.attitude.heading).AsDelta().AbsoluteDegrees() < 5) return; TrackLineRenderer::Draw(canvas, projection.GetScreenAngle(), basic.track, pos); }
bool RaspRenderer::Generate(const WindowProjection &projection, const TerrainRendererSettings &settings) { const auto &style = LookupWeatherTerrainStyle(cache.GetMapName()); const bool do_water = style.do_water; const unsigned height_scale = style.height_scale; const int interp_levels = 5; const ColorRamp *color_ramp = style.color_ramp; const RasterMap *map = cache.GetMap(); if (map == nullptr) return false; if (!map->GetBounds().Overlaps(projection.GetScreenBounds())) /* not visible */ return false; if (color_ramp != last_color_ramp) { raster_renderer.PrepareColorTable(color_ramp, do_water, height_scale, interp_levels); last_color_ramp = color_ramp; } raster_renderer.ScanMap(*map, projection); raster_renderer.GenerateImage(false, height_scale, settings.contrast, settings.brightness, Angle::Zero(), false); return true; }
void AirspaceRenderer::Draw(Canvas &canvas, #ifndef ENABLE_OPENGL Canvas &buffer_canvas, Canvas &stencil_canvas, #endif const WindowProjection &projection, const AirspaceRendererSettings &settings, const AirspaceWarningCopy &awc, const AirspacePredicate &visible) { if (airspace_database == NULL) return; #ifdef ENABLE_OPENGL if (settings.fill_mode == AirspaceRendererSettings::AS_FILL_ALL) { AirspaceFillRenderer renderer(canvas, projection, airspace_look, awc, settings); airspace_database->visit_within_range(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters(), renderer, visible); } else { AirspaceVisitorRenderer renderer(canvas, projection, airspace_look, awc, settings); airspace_database->visit_within_range(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters(), renderer, visible); } #else MapDrawHelper helper(canvas, buffer_canvas, stencil_canvas, projection, settings); AirspaceVisitorMap v(helper, awc, settings, airspace_look); // JMW TODO wasteful to draw twice, can't it be drawn once? // we are using two draws so borders go on top of everything airspace_database->visit_within_range(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters(), v, visible); awc.visit_warned(v); awc.visit_inside(v); v.draw_intercepts(); AirspaceOutlineRenderer outline_renderer(canvas, projection, airspace_look, settings.black_outline); airspace_database->visit_within_range(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters(), outline_renderer, visible); awc.visit_warned(outline_renderer); awc.visit_inside(outline_renderer); #endif m_airspace_intersections = awc.get_locations(); }
/** * Draws the terrain to the given canvas * @param canvas The drawing canvas * @param map_projection The Projection * @param sunazimuth Azimuth of the sun (for terrain shading) */ void TerrainRenderer::Draw(Canvas &canvas, const WindowProjection &map_projection) const { #ifdef ENABLE_OPENGL const GeoBounds &bounds = raster_renderer.GetBounds(); assert(bounds.IsValid()); const RasterPoint vertices[] = { map_projection.GeoToScreen(bounds.GetNorthWest()), map_projection.GeoToScreen(bounds.GetNorthEast()), map_projection.GeoToScreen(bounds.GetSouthWest()), map_projection.GeoToScreen(bounds.GetSouthEast()), }; glVertexPointer(2, GL_VALUE, 0, vertices); const GLTexture &texture = raster_renderer.BindAndGetTexture(); const PixelSize allocated = texture.GetAllocatedSize(); const int src_x = 0, src_y = 0, src_width = raster_renderer.GetWidth(), src_height = raster_renderer.GetHeight(); GLfloat x0 = (GLfloat)src_x / allocated.cx; GLfloat y0 = (GLfloat)src_y / allocated.cy; GLfloat x1 = (GLfloat)(src_x + src_width) / allocated.cx; GLfloat y1 = (GLfloat)(src_y + src_height) / allocated.cy; const GLfloat coord[] = { x0, y0, x1, y0, x0, y1, x1, y1, }; OpenGL::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); GLEnable scope(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, 0, coord); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableClientState(GL_TEXTURE_COORD_ARRAY); #else CopyTo(canvas, map_projection.GetScreenWidth(), map_projection.GetScreenHeight()); #endif }
inline void AirspaceRenderer::DrawOutline(Canvas &canvas, const WindowProjection &projection, const AirspaceRendererSettings &settings, const AirspacePredicate &visible) const { const auto range = airspaces->QueryWithinRange(projection.GetGeoScreenCenter(), projection.GetScreenDistanceMeters()); AirspaceOutlineRenderer outline_renderer(canvas, projection, look, settings); for (const auto &i : range) { const AbstractAirspace &airspace = i.GetAirspace(); if (visible(airspace)) outline_renderer.Visit(airspace); } }
void TerrainRenderer::Generate(const WindowProjection &map_projection, const Angle sunazimuth) { #ifdef ENABLE_OPENGL const GeoBounds &old_bounds = raster_renderer.GetBounds(); const GeoBounds &new_bounds = map_projection.GetScreenBounds(); assert(new_bounds.IsValid()); if (old_bounds.IsValid() && old_bounds.IsInside(new_bounds) && !IsLargeSizeDifference(old_bounds, new_bounds) && terrain_serial == terrain->GetSerial() && sunazimuth.CompareRoughly(last_sun_azimuth) && !raster_renderer.UpdateQuantisation()) /* no change since previous frame */ return; #else if (compare_projection.Compare(map_projection) && terrain_serial == terrain->GetSerial() && sunazimuth.CompareRoughly(last_sun_azimuth)) /* no change since previous frame */ return; compare_projection = CompareProjection(map_projection); #endif terrain_serial = terrain->GetSerial(); last_sun_azimuth = sunazimuth; const bool do_water = true; const unsigned height_scale = 4; const int interp_levels = 2; const bool is_terrain = true; const bool do_shading = is_terrain && settings.slope_shading != SlopeShading::OFF; const bool do_contour = is_terrain && settings.contours != Contours::OFF; const ColorRamp *const color_ramp = &terrain_colors[settings.ramp][0]; if (color_ramp != last_color_ramp) { raster_renderer.ColorTable(color_ramp, do_water, height_scale, interp_levels); last_color_ramp = color_ramp; } { RasterTerrain::Lease map(*terrain); raster_renderer.ScanMap(map, map_projection); } raster_renderer.GenerateImage(do_shading, height_scale, settings.contrast, settings.brightness, sunazimuth, do_contour); }
void AirspaceRenderer::DrawIntersections(Canvas &canvas, const WindowProjection &projection) const { for (unsigned i = intersections.size(); i--;) { RasterPoint sc; if (projection.GeoToScreenIfVisible(intersections[i], sc)) look.intercept_icon.Draw(canvas, sc.x, sc.y); } }
AirspaceFillRenderer(Canvas &_canvas, const WindowProjection &_projection, const AirspaceLook &_look, const AirspaceWarningCopy &_warnings, const AirspaceRendererSettings &_settings) :MapCanvas(_canvas, _projection, _projection.GetScreenBounds().Scale(fixed(1.1))), look(_look), warning_manager(_warnings), settings(_settings) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); }
void Marks::Draw(Canvas &canvas, const WindowProjection &projection) { Poco::ScopedRWLock protect(lock, false); // read only for (unsigned i = 0; i < marker_store.size(); i++) { RasterPoint sc; if (projection.GeoToScreenIfVisible(marker_store[i], sc)) icon.draw(canvas, sc); } }
static void RenderMarkers(Canvas &canvas, const WindowProjection &projection, const MarkerLook &look, const Markers &markers) { for (const Marker &m : markers) { RasterPoint pt; if (projection.GeoToScreenIfVisible(m.location, pt)) look.icon.Draw(canvas, pt); } }
AirspaceOutlineRenderer(Canvas &_canvas, const WindowProjection &_projection, const AirspaceLook &_look, const AirspaceRendererSettings &_settings) :MapCanvas(_canvas, _projection, _projection.GetScreenBounds().Scale(fixed(1.1))), look(_look), settings(_settings) { if (settings.black_outline) canvas.SelectBlackPen(); canvas.SelectHollowBrush(); }