indexing description: "[ Drawing surface. TODO: write explanation of "draw_*" and "put_*" ]" date: "$Date$" revision: "$Revision$" class EM_SURFACE inherit EM_SHARED_SUBSYSTEMS export {NONE} all end EM_SHARED_BITMAP_FACTORY export {NONE} all end EM_SHARED_ERROR_HANDLER export {NONE} all end EM_CONSTANTS export {NONE} all end SDL_SURFACE_STRUCT_EXTERNAL export {NONE} all end SDL_FUNCTIONS_EXTERNAL export {NONE} all end SDL_RWOPS_FUNCTIONS_EXTERNAL export {NONE} all end SDL_VIDEO_FUNCTIONS_EXTERNAL export {NONE} all end SDL_GFXPRIMITIVES_FUNCTIONS_EXTERNAL export {NONE} all end SDL_ROTOZOOM_FUNCTIONS_EXTERNAL export {NONE} all end EM_VIDEO_FUNCTIONS_EXTERNAL export {NONE} all end MEMORY export {NONE} all redefine dispose end DOUBLE_MATH export {NONE} all end create {EM_BITMAP_FACTORY} make_from_pointer feature {NONE} -- Initialization make_from_pointer (a_pointer: POINTER) is -- Make from `a_pointer' that is possibly shared within the -- process of the current OS. require a_pointer_not_void: a_pointer /= Default_pointer do item := a_pointer -- Pre-create rectangle for getting and setting clipping area. create clipping_.make (0, 0, 0, 0) -- Set anti aliasing to the same as on `video_surface' or `True' as default. if Video_subsystem.video_surface /= Void then is_anti_aliasing_enabled := Video_subsystem.video_surface.is_anti_aliasing_enabled else is_anti_aliasing_enabled := True end line_width := 1.0 zoom_factor_x := 1.0 zoom_factor_y := 1.0 create drawing_color.make_white create visible_area.make_from_coordinates (0, 0, width, height) is_line_point_rounding_enabled := True is_line_width_scaling_enabled := True end feature -- Access flags: INTEGER is -- Access member `flags' require exists: exists do Result := get_flags_external (item) ensure result_correct: Result = get_flags_external (item) end format: POINTER is -- Access member `format' require exists: exists do Result := get_format_external (item) ensure result_correct: Result = get_format_external (item) end width: INTEGER is -- Access member `width' require exists: exists do Result := get_w_external (item) ensure result_correct: Result = get_w_external (item) end height: INTEGER is -- Access member `height' require exists: exists do Result := get_h_external (item) ensure result_correct: Result = get_h_external (item) end pitch: INTEGER is -- Access member `pitch' require exists: exists do Result := get_pitch_external (item) ensure result_correct: Result = get_pitch_external (item) end pixels: POINTER is -- Access member `pixels' require exists: exists do Result := get_pixels_external (item) ensure result_correct: Result = get_pixels_external (item) end offset: INTEGER is -- Access member `offset' require exists: exists do Result := get_offset_external (item) ensure result_correct: Result = get_offset_external (item) end hwdata: POINTER is -- Access member `hwdata' require exists: exists do Result := get_hwdata_external (item) ensure result_correct: Result = get_hwdata_external (item) end unused1: INTEGER is -- Access member `unused1' require exists: exists do Result := get_unused1_external (item) ensure result_correct: Result = get_unused1_external (item) end locked: INTEGER is -- Access member `locked' require exists: exists do Result := get_locked_external (item) ensure result_correct: Result = get_locked_external (item) end map: POINTER is -- Access member `map' require exists: exists do Result := get_map_external (item) ensure result_correct: Result = get_map_external (item) end format_version: INTEGER is -- Access member `format_version' require exists: exists do Result := get_format_version_external (item) ensure result_correct: Result = get_format_version_external (item) end refcount: INTEGER is -- Access member `refcount' require exists: exists do Result := get_refcount_external (item) ensure result_correct: Result = get_refcount_external (item) end drawing_color: EM_COLOR -- Color used to draw line_width: DOUBLE -- Line width used to draw lines device_resolution: DOUBLE is -- Number of pixels/points on device per user coordinate unit -- (Usefull for drawing with adopted level of detail -- for better performance) do Result := sqrt (width * height / visible_area.size) ensure positive_display_resolution: Result > 0 end device_line_width: INTEGER is -- Line width used to draw lines with `line_width' onto `surface'. do if is_line_width_scaling_enabled then Result := (line_width * device_resolution).rounded else Result := line_width.rounded end end coordinate_area: EM_RECTANGLE is -- Coordinates inside which all drawing primitives can draw, -- coordinates outside this area will be clipped -- (usefull to avoid drawing objects, that are anyway off-screen), -- `coordinate_area' can be changed using `transform_coordinates', -- `translate_coordinates' and `clip_coordinates'. local ul, lr: EM_VECTOR_2D clip_box: EM_BLITTING_RECTANGLE do clip_box := clipping_rectangle ul := user_point (clip_box.x, clip_box.y) lr := user_point (clip_box.x + clip_box.width, clip_box.y + clip_box.height) create Result.make (ul, lr) ensure result_not_void: Result /= Void end clipping_rectangle: EM_BLITTING_RECTANGLE is -- Clipping rectangle. Pixels outside this rectangle won't be drawn. -- (see `coordinate_area' for according rectangle in user coordinates) do sdl_get_clip_rect_external (item, clipping_.item) Result := clipping_ ensure result_not_void: Result /= Void end resolution: INTEGER is -- Number of bytes per pixel do result := pixel_format.bytes_per_pixel end pixel_format: EM_PIXELFORMAT is -- Informations about the pixel format of `Current' do Result := create {EM_PIXELFORMAT}.make (format) ensure result_not_void: Result /= Void end pixel_value (an_x, an_y: INTEGER): INTEGER is -- Color value of the pixel at position `an_x', `an_y' in `Current' -- Note: For direct pixel access `Current' has to be locked using `lock'. require surface_locked: is_locked position_valid: an_x < width and an_x >= 0 and an_y < height and an_y >= 0 local pointer: POINTER typed_pointer: TYPED_POINTER[INTEGER] do pointer := pixels + (an_y * pitch) + (an_x * resolution) ($Result).memory_copy (pointer, resolution) end pixel_color (an_x, an_y: INTEGER): EM_COLOR is -- Color value of the pixel at position `an_x', `an_y' in `Current' -- Note: For direct pixel access `Current' has to be locked using `lock'. require surface_locked: is_locked position_valid: an_x < width and an_x >= 0 and an_y < height and an_y >= 0 do Result := pixel_format.pixel_value_to_color (pixel_value (an_x, an_y)) end transparent_colorkey: INTEGER is -- Value of the color that is drawed as transparent when drawing `Current' -- onto another surface. do Result := pixel_format.colorkey end alpha_value: INTEGER is -- Alpha value for `Current' (per surface) -- This value will only be used in blitting when alpha transparency is enabled! do Result := pixel_format.alpha end lock_calls: INTEGER -- Number of times `lock' was called without calling `unlock'. texture: GL_TEXTURE is -- Texture of surface for OpenGL do if internal_texture = Void then create internal_texture.make_from_surface (Current) end Result := internal_texture ensure texture_not_void: Result /= Void end rotation_angle: DOUBLE -- Rotation angle of `Current' in degree zoom_factor_x: DOUBLE -- Zoom factor of `Current' (x-axis) zoom_factor_y: DOUBLE -- Zoom factor of `Current' (y-axis) feature -- Status report exists: BOOLEAN is -- Is this surface attached to a corresponding SDL object? do Result := item /= Default_pointer ensure definition: Result = (item /= Default_pointer) end is_anti_aliasing_enabled: BOOLEAN -- Is anti aliasing enabled for converting `Current' and for drawing onto it? is_line_width_scaling_enabled: BOOLEAN -- Does `Current' scale line width, -- when coordinates are scaled? -- (`True' by default) is_line_point_rounding_enabled: BOOLEAN -- Are points of polylines and line segments drawed rounded -- for nice line endings and joins between polyline segments? is_rotated: BOOLEAN -- Is `Current' rotated? is_zoomed: BOOLEAN -- Is `Current' zoomed? is_locked: BOOLEAN is -- Is `Current' locked? do Result := lock_calls > 0 end is_alpha_transparency_enabled: BOOLEAN is -- Is alpha transparency enabled? -- (either per surface or per pixel) do Result := get_flags_external (item) & Em_srcalpha /= 0 end is_per_pixel_alpha_enabled: BOOLEAN is -- Is alpha transparency on a per pixel basis enabled? do Result := is_alpha_transparency_enabled and pixel_format.alpha_mask /= 0 end is_per_surface_alpha_enabled: BOOLEAN is -- Is alpha transparency for `Current' enabled? do Result := is_alpha_transparency_enabled and not is_per_pixel_alpha_enabled end has_transparent_colorkey: BOOLEAN -- Has `Current' a `transparent_colorkey'? feature {GL_TEXTURE} -- Status report is_modified: BOOLEAN -- Was surface modified since last `set_unmodified'? feature -- Status setting set_line_width_scaling_enabled (a_bool: BOOLEAN) is -- Set `is_line_width_scaling_enabled' to `a_bool'. do is_line_width_scaling_enabled := a_bool ensure is_line_width_scaling_enabled_set: is_line_width_scaling_enabled = a_bool end set_line_point_rounding_enabled (a_bool: BOOLEAN) is -- Set `is_line_point_rounding_enabled' to `a_bool'. do is_line_point_rounding_enabled := a_bool ensure is_line_point_rounding_enabled_set: is_line_point_rounding_enabled = a_bool end set_anti_aliasing_enabled (a_bool: BOOLEAN) is -- Set `is_anti_aliasing_enabled' to `a_bool'. do is_anti_aliasing_enabled := a_bool ensure is_anti_aliasing_enabled_set: is_anti_aliasing_enabled = a_bool end enable_anti_aliasing is -- Enable antialiasing on `Current'. do is_anti_aliasing_enabled := True ensure antialiasing_enabled: is_anti_aliasing_enabled end disable_anti_aliasing is -- Disable antialiasing on `Current'. do is_anti_aliasing_enabled := False ensure antialiasing_disabled: not is_anti_aliasing_enabled end disable_transparent_colorkey is -- Disable transparent colorkey for blitting `Current' surface. do if sdl_set_color_key_external (item, 0, transparent_colorkey) < 0 then Error_handler.raise_error (Error_handler.Em_error_disable_transparent_colorkey, []) end has_transparent_colorkey := False ensure transparent_colorkey_disabled: not has_transparent_colorkey end enable_alpha_transparency is -- Enable alpha transparency for `Current'. do if sdl_set_alpha_external (item, Em_srcalpha, alpha_value) < 0 then Error_handler.raise_error (Error_handler.Em_error_enable_alpha_transparency, []) end ensure alpha_transparency_enabled: is_alpha_transparency_enabled end disable_alpha_transparency is -- Disable alpha transparency for `Current'. do if sdl_set_alpha_external (item, 0, 0) < 0 then Error_handler.raise_error (Error_handler.Em_error_disable_alpha_transparency, []) end ensure alpha_transparency_disabled: not is_alpha_transparency_enabled end enable_per_pixel_alpha is -- Enable per pixel alpha transparency. -- Note: A `pixel_format' object obtained before calling `enable_per_pixel_alpha' will be invalid. local new_surface: POINTER do new_surface := sdl_display_format_alpha_external (item) if new_surface = Default_pointer then Error_handler.raise_error (Error_handler.Em_error_enable_per_pixel_alpha, []) end -- Free old surface sdl_free_surface_external (item) -- Set 'item' to 'new_surface' item := new_surface ensure alpha_transparency_enabled: is_alpha_transparency_enabled per_pixel_alpha_enabled: is_per_pixel_alpha_enabled per_surface_alpha_disabled: not is_per_surface_alpha_enabled end disable_per_pixel_alpha is -- Disable per pixel alpha transparency do -- TODO: maybe a better implementation would be to use 'sdl_display_format' and copy -- the whole surface without alpha channel. if is_per_pixel_alpha_enabled then pixel_format.set_alpha_mask (0) end ensure per_pixel_alpha_disabled: not is_per_pixel_alpha_enabled end feature {GL_TEXTURE} -- Status setting set_unmodified is -- Clear modified status. do is_modified := False ensure not_modified: not is_modified end feature -- Element change set_drawing_color (a_color: like drawing_color) is -- Set `drawing_color' to `a_color'. require a_color_attached: a_color /= Void do drawing_color := a_color ensure drawing_color_set: drawing_color.is_equal (a_color) end set_line_width (a_width: like line_width) is -- Set `line_width' to `a_width'. do line_width := a_width ensure line_width_assigned: line_width = a_width end set_transparent_colorkey (a_colorkey: like transparent_colorkey) is -- Set color `a_colorkey' to be transparent in `Current'. -- This will disable per pixel alpha transparency on the surface. local success: INTEGER do success := sdl_set_color_key_external (item, Em_colorkey, a_colorkey) if success < 0 then Error_handler.raise_error (Error_handler.Em_error_set_transparent_colorkey, [a_colorkey]) end has_transparent_colorkey := True is_modified := True disable_per_pixel_alpha ensure per_pixel_alpha_disabled: not is_per_pixel_alpha_enabled has_transparent_colorkey: has_transparent_colorkey transparent_colorkey_set: transparent_colorkey = a_colorkey end set_transparent_color (red: INTEGER; green:INTEGER; blue: INTEGER) is -- Set color (`red', `green', `blue') to be transparent in `Current'. -- This will disable per pixel alpha transparency on the surface. require red_not_negative: red >= 0 green_not_negative: green >= 0 blue_not_negative: blue >= 0 local colkey: INTEGER do colkey := sdl_map_rgb_external (pixel_format.sdl_pixel_format_struct.item, red, green, blue) set_transparent_colorkey (colkey) ensure per_pixel_alpha_disabled: not is_per_pixel_alpha_enabled has_transparent_colorkey: has_transparent_colorkey end set_alpha_value (an_alpha_value: like alpha_value) is -- Enable alpha transparency for `Current' and set `alpha_value' to `an_alpha_value'. -- This setting is for the whole surface and cannot be used to set per pixel alpha transparency. -- alpha = 0 is transparent -- alpha = 255 is opaque -- Note: an alpha value of 128 is treated special and is optimised -- Note: this does NOT work in OpenGL mode if `Current' will be blitted directly on the screen -- Note: this will disable per pixel alpha require an_alpha_value_in_range: 0 <= an_alpha_value and an_alpha_value <= 255 do if sdl_set_alpha_external (item, Em_srcalpha, an_alpha_value) = -1 then Error_handler.raise_error (Error_handler.Em_error_set_alpha_value, [an_alpha_value]) end disable_per_pixel_alpha ensure alpha_value_set: alpha_value = an_alpha_value alpha_transparency_enabled: is_alpha_transparency_enabled per_surface_alpha_enabled: is_per_surface_alpha_enabled per_pixel_alpha_disabled: not is_per_pixel_alpha_enabled end set_clipping_rectangle (a_rectangle: like clipping_rectangle) is -- Set `clipping_rectangle' to `a_rectangle'. require a_rectangle_not_void: a_rectangle /= Void local success: INTEGER do success := sdl_set_clip_rect_external (item, a_rectangle.item) end set_flags (a_value: INTEGER) is -- Set member `flags' require exists: exists do set_flags_external (item, a_value) ensure a_value_set: a_value = flags end set_refcount (a_value: INTEGER) is -- Set member `refcount' require exists: exists do set_refcount_external (item, a_value) ensure a_value_set: a_value = refcount end set_format (a_value: POINTER) is -- Set member `format' require exists: exists do set_format_external (item, a_value) ensure a_value_set: a_value = format end set_width (a_value: INTEGER) is -- Set member `width' require exists: exists do set_w_external (item, a_value) ensure a_value_set: a_value = width end set_height (a_value: INTEGER) is -- Set member `height' require exists: exists do set_h_external (item, a_value) ensure a_value_set: a_value = height end set_pitch (a_value: INTEGER) is -- Set member `pitch' require exists: exists do set_pitch_external (item, a_value) ensure a_value_set: a_value = pitch end set_pixels (a_value: POINTER) is -- Set member `pixels' require exists: exists do set_pixels_external (item, a_value) ensure a_value_set: a_value = pixels end set_offset (a_value: INTEGER) is -- Set member `offset' require exists: exists do set_offset_external (item, a_value) ensure a_value_set: a_value = offset end set_hwdata (a_value: POINTER) is -- Set member `hwdata' require exists: exists do set_hwdata_external (item, a_value) ensure a_value_set: a_value = hwdata end set_unused1 (a_value: INTEGER) is -- Set member `unused1' require exists: exists do set_unused1_external (item, a_value) ensure a_value_set: a_value = unused1 end set_locked (a_value: INTEGER) is -- Set member `locked' require exists: exists do set_locked_external (item, a_value) ensure a_value_set: a_value = locked end set_map (a_value: POINTER) is -- Set member `map' require exists: exists do set_map_external (item, a_value) ensure a_value_set: a_value = map end set_format_version (a_value: INTEGER) is -- Set member `format_version' require exists: exists do set_format_version_external (item, a_value) ensure a_value_set: a_value = format_version end feature -- Transformation zoom (factor: DOUBLE) is -- Zoom `Current' with `factor'. -- Note: If antialiasing is enabled on `Current' the zooming can be slow (use prerendering if possible). -- Note: In most cases this feature changes the dimension of `Current'. -- Note: After zooming, no drawing is allowed on `Current' (but you still can zoom and rotate). do zoom_factor_x := zoom_factor_x * factor zoom_factor_y := zoom_factor_y * factor apply_rotozoom is_zoomed := True ensure zoom_factor_x_set: zoom_factor_x = old zoom_factor_x * factor zoom_factor_y_set: zoom_factor_y = old zoom_factor_y * factor zoomed: is_zoomed end stretch (factor_x, factor_y: DOUBLE) is -- Strech `Current' with `factor_x' `factor_y'. -- Note: If antialiasing is enabled on `Current' the zooming can be slow (use prerendering if possible). -- Note: In most cases this feature changes the dimension of `Current'. -- Note: After zooming, no drawing is allowed on `Current' (but you still can zoom and rotate). do zoom_factor_x := zoom_factor_x * factor_x zoom_factor_y := zoom_factor_y * factor_y apply_rotozoom is_zoomed := True ensure zoom_factor_x_set: zoom_factor_x = old zoom_factor_x * factor_x zoom_factor_y_set: zoom_factor_y = old zoom_factor_y * factor_y zoomed: is_zoomed end rotate (angle: DOUBLE) is -- Rotate `Current' counterclockwise `angle' degrees. -- Note: If antialiasing is enabled on `Current' the rotation can be slow (use prerendering if possible). -- Note: In most cases this feature changes the dimension of `Current'. -- Note: After rotating, no drawing is allowed on `Current' (but you still can zoom and rotate). do rotation_angle := rotation_angle + angle apply_rotozoom is_rotated := True ensure rotation_angle_set: rotation_angle = old rotation_angle + angle rotated: is_rotated end transform (factor_x, factor_y, angle: DOUBLE) is -- Transform `Current' with stretchfactors `factor_x' `factor_y' and -- a counterclockwise rotation `angle' degrees. -- Note: If antialiasing is enabled on `Current' the rotation can be slow (use prerendering if possible). -- Note: In most cases this feature changes the dimension of `Current'. -- Note: After rotating, no drawing is allowed on `Current' (but you still can zoom and rotate). do zoom_factor_x := zoom_factor_x * factor_x zoom_factor_y := zoom_factor_y * factor_y rotation_angle := rotation_angle + angle apply_rotozoom is_rotated := True is_zoomed := True ensure zoom_factor_x_set: zoom_factor_x = old zoom_factor_x * factor_x zoom_factor_y_set: zoom_factor_y = old zoom_factor_y * factor_y rotation_angle_set: rotation_angle = old rotation_angle + angle zoomed: is_zoomed rotated: is_rotated end feature -- Conversion device_point (a_point: EM_VECTOR_2D): EM_VECTOR_2D is -- Device point where `a_point' gets drawed to require a_point_not_void: a_point /= Void local px, py: DOUBLE do px := (a_point.x - visible_area.left_bound) * width / visible_area.width py := (a_point.y - visible_area.upper_bound) * height / visible_area.height create Result.make (px, py) ensure result_calculated: Result /= Void end user_point (a_x, a_y: DOUBLE): EM_VECTOR_2D is -- Point in user coordinates that gets drawed -- to device point at `x' and `y' local px, py: DOUBLE do px := (a_x * visible_area.width / width) + visible_area.left_bound py := (a_y * visible_area.height / height) + visible_area.upper_bound create Result.make (px, py) ensure result_calculated: Result /= Void end feature -- Miscellaneous save_to_bmp( filename: STRING ) is -- Save the Surface to a file require filename/=void local filename_c_string: EWG_ZERO_TERMINATED_STRING mode_c_string: EWG_ZERO_TERMINATED_STRING rw: POINTER do create filename_c_string.make_shared_from_string (filename) create mode_c_string.make_shared_from_string ("wb") -- Command "copied" from SDL.h (makro) rw := sdl_rwfrom_file_external ( filename_c_string.item, mode_c_string.item ) if sdl_save_bmp_rw_external ( item, rw, 1) = -1 then Error_handler.raise_warning (Error_handler.Em_error_save_surface, [filename]) end end save_to_png ( filename: STRING ) is -- Safe the surface to a PNG image require filename/=void local filename_c_string: EWG_ZERO_TERMINATED_STRING do create filename_c_string.make_unshared_from_string ( filename ) if save_surface_as_png_external( item, filename_c_string.item ) /= 0 then Error_handler.raise_warning (Error_handler.Em_error_save_surface, [filename]) end end lock is -- Lock `Current' to enable direct pixel access. -- Note: Every `lock' must have an `unlock'. -- If `lock' is called multiple times then so must `unlock' do if sdl_lock_surface_external (item) < 0 then Error_handler.raise_error (Error_handler.Em_error_locking_surface, []) end lock_calls := lock_calls + 1 ensure lock_calls_increased: lock_calls = old lock_calls + 1 locked: is_locked end unlock is -- Unlock `Current'. -- Note: `Current' is only unlocked if `unlock' is called `lock_calls' times. require surface_locked: is_locked do sdl_unlock_surface_external (item) lock_calls := lock_calls - 1 ensure lock_calls_decreased: lock_calls = old lock_calls - 1 end clip_coordinates (an_area: EM_RECTANGLE) is -- Clip `coordinate_area' to `an_area'. -- Usefull to restrict drawing to `an_area' -- and to just not draw anything outside. -- (Reset by calling again with backed up old `coordinate_area' -- when done clipping) require an_area_not_void: an_area /= Void local ul, lr: EM_VECTOR_2D do ul := device_point (an_area.upper_left) lr := device_point (an_area.lower_right) internal_clipping_rect.set_x (ul.x.rounded) internal_clipping_rect.set_y (ul.y.rounded) internal_clipping_rect.set_width (lr.x.rounded - ul.x.rounded) internal_clipping_rect.set_height (lr.y.rounded - ul.y.rounded) set_clipping_rectangle (internal_clipping_rect) end translate_coordinates (a_distance: EM_VECTOR_2D) is -- Draw all following drawing primitives as translated by `a_distance'. -- Usefull for container drawables that want there children to -- be drawed relatively to a new origin-point. -- (Reset by calling again with negation of `a_distance' when done) require a_distance_not_void: a_distance /= Void do visible_area.move_by (- a_distance) end transform_coordinates (an_area: EM_RECTANGLE) is -- Transform `coordinate_area' to `an_area'. -- Usefull for drawable containers that want there children to -- be drawed scaled (zoomed or stretched). -- (Reset it by calling again with backed up old `coordinate_area' when done). require an_area_not_void: an_area /= Void local clip_box: EM_BLITTING_RECTANGLE clip_upper, clip_left, clip_width, clip_height: DOUBLE vx, vy, vw, vh: DOUBLE do -- Calculate new `visible_area', such as `coordinate_area' gets transformed to `an_area' -- (set up such as to project an_area to the clipping box). clip_box := clipping_rectangle clip_upper := clip_box.y clip_left := clip_box.x clip_width := clip_box.width clip_height := clip_box.height vw := width * an_area.width / clip_width vh := height * an_area.height / clip_height vx := an_area.left_bound - (clip_left * vw / width) vy := an_area.upper_bound - (clip_upper * vh / height) visible_area.make_from_coordinates (vx, vy, vx + vw, vy + vh) end disable_scaling_coordinates (a_reference_point: EM_VECTOR_2D) is -- Transform coordinates such as no scaling is done anymore and use -- `a_reference_point' as the point that keeps its position. -- (Reset by calling `transform_coordinates' with backed up -- old `coordinate_area' when done) require a_reference_point_not_void: a_reference_point /= Void local projected_reference, upper_left, lower_right: EM_VECTOR_2D do projected_reference := device_point (a_reference_point) upper_left := a_reference_point - projected_reference create lower_right.make (upper_left.x + width, upper_left.y + height) visible_area.make (upper_left, lower_right) end feature -- Drawing fill (a_color: EM_COLOR) is -- Fill surface with `a_color'. require a_color_not_void: a_color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local i:INTEGER color: INTEGER do color := sdl_map_rgb_external (format, a_color.red, a_color.green, a_color.blue) i := sdl_fill_rect_external (item, default_pointer, color) is_modified := True end clear is -- Clear `Current' surface with black color. require not_rotated: not is_rotated not_zoomed: not is_zoomed local i: INTEGER do i := sdl_fill_rect_external (item, default_pointer, 0) is_modified := True end feature -- Drawing (with coordinate transformation) draw_point (a_point: EM_VECTOR_2D) is -- Draw a point at position `a_point' with width `line_width' using `drawing_color'. -- Draw exactly one pixel if `line_width' is 0.0. require a_point_attached: a_point /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local pp: EM_VECTOR_2D a_width: DOUBLE do -- Projection. pp := device_point (a_point) -- Drawing. a_width := device_line_width / 2 if a_width < 1.0 then put_pixel (pp.x.floor, pp.y.floor, drawing_color) else put_circle (pp.x.floor, pp.y.floor, a_width.floor, drawing_color) end end draw_line_segment (point1, point2: EM_VECTOR_2D) is -- Draw line segment from `point1' to `point2' using current `line_width' and `drawing_color'. require point1_attached: point1 /= Void point2_attached: point2 /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local p1, p2: EM_VECTOR_2D left, right, upper, lower: DOUBLE clip_box: EM_RECTANGLE -- p1x, p1y, p2x, p2y, dx, dy, dfact: DOUBLE p1x, p1y, p2x, p2y: INTEGER do -- Prepare for rough line clipping: -- Just clip to coordinate_area in world_coordinates plus a `line_width' border, -- (this is much easier, because possible without considering the line width) -- this rough clipping is sufficiant, since SDL does an exactly per pixel clipping, -- but anyway needed because SDL clipping alone can get very slow for large polygons -- and uncliped coordinates transformed to display coordinates could run into infinity -- (i.e. for very large polyline segments) which would lead to wrong (ugly) results. clip_box := coordinate_area left := clip_box.left_bound - line_width right := clip_box.right_bound + line_width upper := clip_box.upper_bound - line_width lower := clip_box.lower_bound + line_width create p1.make (0, 0) create p2.make (0, 0) clip_line_segment (point1.x, point1.y, point2.x, point2.y, left, upper, right, lower, p1, p2) -- Only draw if different points. if p1.x /= p2.x or else p1.y /= p2.y then -- Project points to surface coordinates. p1 := device_point (p1) p2 := device_point (p2) p1x := p1.x.floor p1y := p1.y.floor p2x := p2.x.floor p2y := p2.y.floor put_wide_line_segment (p1x, p1y, p2x, p2y, device_line_width, drawing_color) if is_line_point_rounding_enabled and then device_line_width > 1 then put_circle_filled (p1x, p1y, device_line_width // 2, drawing_color) put_circle_filled (p2x, p2y, device_line_width // 2, drawing_color) end end end draw_rectangle (a_rectangle: EM_RECTANGLE) is -- Draw `a_rectangle' with `line_width' and `drawing_color'. require a_rectangle_not_void: a_rectangle /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local ul, lr: EM_VECTOR_2D x1, y1, x2, y2: INTEGER clip_box: EM_RECTANGLE clipped_rectangle: EM_RECTANGLE left, right, upper, lower: DOUBLE do -- Clip first in user coordinates. clip_box := coordinate_area left := clip_box.left_bound - line_width right := clip_box.right_bound + line_width upper := clip_box.upper_bound - line_width lower := clip_box.lower_bound + line_width create clip_box.make_from_coordinates (left, upper, right, lower) clipped_rectangle := a_rectangle.intersection (clip_box) -- Project points. ul := device_point (clipped_rectangle.upper_left) lr := device_point (clipped_rectangle.lower_right) -- Calculate device coordinates x1 := ul.x.floor y1 := ul.y.floor x2 := lr.x.floor y2 := lr.y.floor if device_line_width > 1 then -- Draw according line segments. put_wide_line_segment (x1, y1, x2, y1, device_line_width, drawing_color) put_wide_line_segment (x2, y1, x2, y2, device_line_width, drawing_color) put_wide_line_segment (x2, y2, x1, y2, device_line_width, drawing_color) put_wide_line_segment (x1, y2, x1, y1, device_line_width, drawing_color) -- Draw rounded corners with circles, if needed. if is_line_point_rounding_enabled and then device_line_width > 1 then put_circle_filled (x1, y1, device_line_width // 2, drawing_color) put_circle_filled (x1, y2, device_line_width // 2, drawing_color) put_circle_filled (x2, y2, device_line_width // 2, drawing_color) put_circle_filled (x2, y1, device_line_width // 2, drawing_color) end else put_rectangle (x1, y1, x2, y2, drawing_color) end end fill_rectangle (a_rectangle: EM_RECTANGLE) is -- Draw filled rectangle `a_rectangle' in `drawing_color'. require a_rectangle_not_void: a_rectangle /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local ul, lr: EM_VECTOR_2D x1, y1, x2, y2: INTEGER clipped_rectangle: EM_RECTANGLE do -- Clip first in user coordinates. clipped_rectangle := a_rectangle.intersection (coordinate_area) -- Project points. ul := device_point (clipped_rectangle.upper_left) lr := device_point (clipped_rectangle.lower_right) -- Calculate device coordinates x1 := ul.x.floor y1 := ul.y.floor x2 := lr.x.floor y2 := lr.y.floor -- Draw according rectangle put_rectangle_filled (x1, y1, x2, y2, drawing_color) end draw_polyline (points: DS_LINEAR [EM_VECTOR_2D]) is -- Draw line segments between subsequent points in `points'. require at_least_two_points: points /= Void and then points.count >= 2 not_rotated: not is_rotated not_zoomed: not is_zoomed do -- Just call internal implementation to draw the polyline without closing it. draw_polyline_internal (points, False) end draw_polygon (points: DS_LINEAR [EM_VECTOR_2D]) is -- Draw polygon defined by `points' with `line_width' and `drawing_color'. require at_least_three_points: points /= Void and then points.count >= 3 not_rotated: not is_rotated not_zoomed: not is_zoomed do -- Just call internal implementation to draw the polyline with ensuring to close it. draw_polyline_internal (points, True) end fill_polygon (points: DS_LINEAR [EM_VECTOR_2D]) is -- Draw filled polygon defined by `points' in `drawing_color'. require at_least_three_points: points /= Void and then points.count >= 3 not_rotated: not is_rotated not_zoomed: not is_zoomed local j: INTEGER x_coords, y_coords: EM_INTEGER_ARRAY p, prev: EM_VECTOR_2D clip_area: EM_RECTANGLE clip_left, clip_right, clip_lower, clip_upper: DOUBLE px, py: DOUBLE cursor: DS_LINEAR_CURSOR [EM_VECTOR_2D] end_reached, finished: BOOLEAN do -- Get `clip_area' for clipping in user coordinates -- (ensures that projection does not get out of bounds). clip_area := coordinate_area clip_left := clip_area.left_bound clip_right := clip_area.right_bound clip_upper := clip_area.upper_bound clip_lower := clip_area.lower_bound -- Create SDL compatible arrays for x and y cordinates of clipped and projected polygon `points'. x_coords := big_enough_x_coords_array (2 * points.count) y_coords := big_enough_y_coords_array (2 * points.count) -- Go through all points: clip and project them into the SDL compatible arrays for putting onto surface. cursor := points.new_cursor from cursor.start j := 0 prev := points.first cursor.forth finished := cursor.after until finished loop if cursor.after then cursor.start end_reached := True end p := cursor.item if p.is_equal (prev) then -- Ignore duplicate points in polygon / polyline. cursor.forth else -- Check for edge clipping. prev := polygon_edge_clipping_point (prev.x, prev.y, p.x, p.y, clip_left, clip_upper, clip_right, clip_lower) if prev /= Void then -- Calculate next polygon point `p' from clipping point `prev' -- (just move `prev' onto `clip_area' if not yet). px := prev.x py := prev.y if px < clip_left then px := clip_left elseif px > clip_right then px := clip_right end if py < clip_upper then py := clip_upper elseif py > clip_lower then py := clip_lower end create p.make (px, py) else -- Go to next point, if no clipping for this segment needed anymore. px := p.x py := p.y prev := p cursor.forth if end_reached then finished := True end end -- Project `p' and add it, if it is in `clip_area' (otherwise ignore). if px >= clip_left and then px <= clip_right and then py >= clip_upper and then py <= clip_lower then p := device_point (p) x_coords.put (p.x.floor, j) y_coords.put (p.y.floor, j) j := j + 1 end end end -- Fill the polygon. if j >= 3 then put_polygon_filled (x_coords, y_coords, j, drawing_color) end end draw_bitmap (a_bitmap: EM_BITMAP) is -- Draw `a_bitmap' into its `bounding_box' onto `Current'. require a_bitmap_not_void: a_bitmap /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed do draw_surface_stretched (a_bitmap, a_bitmap.bounding_box) end draw_bitmap_part (a_bitmap: EM_BITMAP; part_rectangle: EM_RECTANGLE) is -- Draw part of `a_bitmap' specified by `part_rectangle' -- at its original position inside `a_bitmap.bounding_box' onto `Current'. require a_bitmap_not_void: a_bitmap /= Void part_rectangle_not_void: part_rectangle /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local part_destination: EM_RECTANGLE do part_destination := part_rectangle.twin part_destination.move_by (a_bitmap.bounding_box.upper_left) draw_surface_part_stretched (a_bitmap, part_rectangle, part_destination) end draw_surface_stretched (a_surface: EM_SURFACE; a_destination_box: EM_RECTANGLE) is -- Draw `a_surface' into `a_destination_box' onto `Current', stretch if necessary. require a_surface_not_void: a_surface /= Void a_destination_box_not_void: a_destination_box /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local part_box: EM_RECTANGLE do create part_box.make_from_coordinates (0, 0, a_surface.width, a_surface.height) draw_surface_part_stretched (a_surface, part_box, a_destination_box) end draw_surface_part_stretched (a_surface: EM_SURFACE; part_rectangle: EM_RECTANGLE; a_destination_box: EM_RECTANGLE) is -- Draw part of `a_surface' specified by `part_rectangle' into `a_destination_box' onto `Current', stretch if necessary. require a_surface_not_void: a_surface /= Void part_rectangle_not_void: part_rectangle /= Void a_destination_box_not_void: a_destination_box /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local upper_left, lower_right, vect: EM_VECTOR_2D source_box, destination_box: EM_RECTANGLE visible_source_box, visible_destination_box: EM_RECTANGLE source_width, source_height, dest_width, dest_height, source_x, source_y: INTEGER zoomx, zoomy: DOUBLE clipped_surface, stretched_surface: EM_SURFACE do -- TODO: fix memory hoping when stretched -- TODO: First clipping of `a_destination_box' in user coordinates. -- Calculate `destination_box' (projected `a_destination_box') where `part_rectangle' should be drawed into. upper_left := device_point (a_destination_box.upper_left) lower_right := device_point (a_destination_box.lower_right) create destination_box.make (upper_left, lower_right) -- Calculate `source_box' as the part of `part_rectangle' that intersects with `a_bitmap'. create source_box.make_from_coordinates (0, 0, a_surface.width, a_surface.height) source_box := source_box.intersection (part_rectangle) -- Only continue if `source_box' and `destination_box' have a size bigger than 0. if source_box.size > 0 and then destination_box.size > 0 then -- Calculate zoom factors for stretching. zoomx := destination_box.width / part_rectangle.width zoomy := destination_box.height / part_rectangle.height -- Adjust `destination_box' to `source_box' (clip it to part where `source_box' should reside) vect := source_box.upper_left - part_rectangle.upper_left vect.stretch (zoomx, zoomy) destination_box.move_by (vect) destination_box.set_size (source_box.width * zoomx, source_box.height * zoomy) -- Calculate visible part of `destination_box' --> `visible_destination_box'. create visible_destination_box.make_from_coordinates (0, 0, width, height) visible_destination_box := destination_box.intersection (visible_destination_box) -- Calculate visible part of `source_box' --> `visible_source_box'. -- TODO: use `clipping_rectangle' vect := visible_destination_box.upper_left - destination_box.upper_left vect.stretch (1 / zoomx, 1 / zoomy) create visible_source_box.make (source_box.upper_left + vect, source_box.lower_right.twin) visible_source_box.set_size (visible_destination_box.width / zoomx, visible_destination_box.height / zoomy) -- Only continue if `visible_source_box' and `visible_destination_box' have a size bigger than 0. if visible_destination_box.size > 0 and then visible_source_box.size > 0 then -- Calculate size in pixels of source and destination. dest_width := visible_destination_box.right_bound.ceiling - visible_destination_box.left_bound.floor dest_height := visible_destination_box.lower_bound.ceiling - visible_destination_box.upper_bound.floor source_width := visible_source_box.right_bound.ceiling - visible_source_box.left_bound.floor source_height := visible_source_box.lower_bound.ceiling - visible_source_box.upper_bound.floor -- Recalculate zoom factors to get correct bitmap sizes. zoomx := dest_width / source_width zoomy := dest_height / source_height -- Calculate `stretched_surface'. if zoomx /= 1 or else zoomy /= 1 then -- Get only visible part of `a_surface' to calculate `stretched_surface' -- only when `a_surface' is at least twice as big as visible part. if a_surface.width > source_width * 2 or else a_surface.height > source_height * 2 then source_x := visible_source_box.left_bound.floor source_y := visible_source_box.upper_bound.floor Bitmap_factory.create_bitmap_from_surface_part (a_surface, source_x, source_y, source_width, source_height) if Bitmap_factory.last_bitmap /= Void then check operation_successful: Bitmap_factory.last_bitmap /= Void end clipped_surface := Bitmap_factory.last_bitmap end source_x := 0 source_y := 0 else clipped_surface := a_surface source_x := (visible_source_box.left_bound * zoomx).floor source_y := (visible_source_box.upper_bound * zoomy).floor end Bitmap_factory.create_stretched_bitmap (clipped_surface, zoomx, zoomy) check operation_successful: Bitmap_factory.last_bitmap /= Void end stretched_surface := Bitmap_factory.last_bitmap else stretched_surface := a_surface source_x := visible_source_box.left_bound.floor source_y := visible_source_box.upper_bound.floor end -- Perform blitting of part in `stretched_surface' onto `Current'. blit_surface_part (stretched_surface, source_x, source_y, dest_width, dest_height, visible_destination_box.left_bound.floor, visible_destination_box.upper_bound.floor) end end end draw_object (an_object: EM_DRAWABLE) is -- Draw `an_object'. require an_object_not_void: an_object /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local old_coords: EM_RECTANGLE do -- Disable scaling for fixed-size object. if an_object.is_size_fixed then old_coords := coordinate_area disable_scaling_coordinates (an_object.reference_point) end -- Draw object if intersects with `coordinate_area'. if coordinate_area.intersects (an_object.bounding_box) then an_object.draw (Current) end -- Reset coordinate system, if changed for fixed-size objects. if an_object.is_size_fixed then transform_coordinates (old_coords) end end feature -- Drawing (without coordinate transformation) blit_surface (a_surface: EM_SURFACE; a_x: INTEGER; a_y: INTEGER) is -- Blit `a_surface' at `a_x' and `a_y' onto `Current'. require a_surface_not_void: a_surface /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do rect_.set_x (a_x) rect_.set_y (a_y) rect_.set_width (0) rect_.set_height (0) success := sdl_upper_blit_external (a_surface.item, Default_pointer, item, rect_.item) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_blit_surface, []) end is_modified := True end blit_surface_part (a_surface: EM_SURFACE; a_x, a_y, a_width, a_height: INTEGER; a_dest_x, a_dest_y: INTEGER) is -- Blit `a_surface' part from `a_x' and `a_y' to `a_width' and `a_height' onto `Current' at `dest_x', `dest_y'. require a_surface_not_void: a_surface /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do rect_.set_x (a_x) rect_.set_y (a_y) rect_.set_width (a_width) rect_.set_height (a_height) destination_.set_x (a_dest_x) destination_.set_y (a_dest_y) destination_.set_width (a_width) destination_.set_height (a_height) success := sdl_upper_blit_external (a_surface.item, rect_.item, item, destination_.item) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_blit_surface_part, []) end is_modified := True end put_pixel (x_pos, y_pos: INTEGER; a_color: EM_COLOR) is -- Draw a pixel at pixel position `x_pos', `y_pos' with color `a_color' onto `Current'. require a_color_not_void: a_color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do success := pixel_rgba_external (item, x_pos, y_pos, a_color.red, a_color.green, a_color.blue, a_color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_pixel, []) end is_modified := True end put_pixel_value (x_pos, y_pos: INTEGER; a_color_value: INTEGER) is -- Draw a pixel at pixel position `x_pos', `y_pos' with color value `a_color_value' onto `Current'. require not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do success := pixel_color_external (item, x_pos, y_pos, a_color_value) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_pixel, []) end is_modified := True end put_line_segment (x1, y1, x2, y2: INTEGER; color: EM_COLOR) is -- Draw a line segment from pixel `x1', `y1' to pixel `x2', `y2' with `color' onto `Current'. require not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do if is_anti_aliasing_enabled then success := aaline_rgba_external (item, x1, y1, x2, y2, color.red, color.green, color.blue, color.alpha) else success := line_rgba_external (item, x1, y1, x2, y2, color.red, color.green, color.blue, color.alpha) end if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_line_segment, []) end is_modified := True end put_wide_line_segment (x1, y1, x2, y2: INTEGER; a_line_width: INTEGER; color: EM_COLOR) is -- Draw a line segment from pixel `x1', `y1' to pixel `x2', `y2' -- with `color' and `a_line_width' onto `Current'. -- (Implementation just draws a rectangular polygon for the line segment) require color_not_void: color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local x_plus, x_minus, y_plus, y_minus: INTEGER dx, dy, len: DOUBLE do if x1 /= x2 or else y1 /= y2 then if a_line_width > 1 then -- Calculate offsets to draw line segment. dx := y1 - y2 dy := x2 - x1 len := sqrt (dx^2 + dy^2) dx := (dx / len) * a_line_width / 2 dy := (dy / len) * a_line_width / 2 x_plus := dx.ceiling x_minus := dx.floor y_plus := dy.ceiling y_minus := dy.floor -- Prepare coordinates to draw polygon for line segment. line_segment_x_coords.put (x1 + x_plus, 0) line_segment_y_coords.put (y1 + y_plus, 0) line_segment_x_coords.put (x2 + x_plus, 1) line_segment_y_coords.put (y2 + y_plus, 1) line_segment_x_coords.put (x2 - x_minus, 2) line_segment_y_coords.put (y2 - y_minus, 2) line_segment_x_coords.put (x1 - x_minus, 3) line_segment_y_coords.put (y1 - y_minus, 3) -- Draw line sgement as polygon. put_polygon_filled (line_segment_x_coords, line_segment_y_coords, 4, color) else -- Otherwise just draw it as simple line segment put_line_segment (x1, y1, x2, y2, color) end is_modified := True end end put_rectangle (x1, y1, x2, y2: INTEGER; color: EM_COLOR) is -- Draw rectangle from pixel `x1', `y1' to pixel `x2', `y2' with `color' onto `Current'. require color_not_void: color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do -- Draw rectangle. success := rectangle_rgba_external (item, x1, y1, x2, y2, color.red, color.green, color.blue, color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_rectangle, []) end is_modified := True end put_rectangle_filled (x1, y1, x2, y2: INTEGER; color: EM_COLOR) is -- Draw a filled rectangle from pixel `x1', `y1' -- to pixel `x2', `y2' filled with `color' onto `Current'. require color_not_void: color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do -- Draw filled rectangle. success := box_rgba_external (item, x1, y1, x2, y2, color.red, color.green, color.blue, color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_rectangle_filled, []) end is_modified := True end put_circle (center_x, center_y: INTEGER; radius: INTEGER; color: EM_COLOR) is -- Draw circle with center `center_x', `center_y' and radius `radius' with `color' onto `Current'. require color_not_void: color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do -- Draw circle. if is_anti_aliasing_enabled then success := aacircle_rgba_external (item, center_x, center_y, radius, color.red, color.green, color.blue, color.alpha) else success := aacircle_rgba_external (item, center_x, center_y, radius, color.red, color.green, color.blue, color.alpha) end if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_circle, []) end is_modified := True end put_circle_filled (center_x, center_y: INTEGER; radius: INTEGER; color: EM_COLOR) is -- Draw filled circle around center at `center_x' and `center_y' -- with `radius' filled with `color' onto `Current'. require color_not_void: color /= Void not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do -- Draw filled circle. success := filled_circle_rgba_external (item, center_x, center_y, radius, color.red, color.green, color.blue, color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_circle_filled, []) end -- Draw anti-aliased edge, if `is_anti_aliasing_enabled'. if is_anti_aliasing_enabled then success := aacircle_rgba_external (item, center_x, center_y, radius, color.red, color.green, color.blue, color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_circle_filled, []) end end is_modified := True end put_polygon (x_coordinates, y_coordinates: EM_INTEGER_ARRAY; count: INTEGER; color: EM_COLOR) is -- Draw polygon defined by first `count' points -- in `x_coordinates' and `y_coordinates' -- with `color' onto `Current'. require x_coordinates_not_void: x_coordinates /= Void y_coordinates_not_void: y_coordinates /= Void enough_x_coordinates: x_coordinates.count >= count enough_y_coordinates: y_coordinates.count >= count not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do -- Draw polygon if is_anti_aliasing_enabled then success := aapolygon_rgba_external (item, x_coordinates.array_address, y_coordinates.array_address, count, color.red, color.green, color.blue, color.alpha) else success := polygon_rgba_external (item, x_coordinates.array_address, y_coordinates.array_address, count, color.red, color.green, color.blue, color.alpha) end if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_polygon, []) end is_modified := True end put_polygon_filled (x_coordinates, y_coordinates: EM_INTEGER_ARRAY; count: INTEGER; color: EM_COLOR) is -- Draw filled polygon defined by first `count' points -- in `x_coordinates' and `y_coordinates' -- filled with `color' onto `Current'. require at_least_3_points: count >= 3 x_coordinates_not_void: x_coordinates /= Void y_coordinates_not_void: y_coordinates /= Void enough_x_coordinates: x_coordinates.count >= count enough_y_coordinates: y_coordinates.count >= count not_rotated: not is_rotated not_zoomed: not is_zoomed local success: INTEGER do -- Draw filled polygon. success := filled_polygon_rgba_external (item, x_coordinates.array_address, y_coordinates.array_address, count, color.red, color.green, color.blue, color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_polygon_filled, []) end -- Draw anti-aliased edge, if `is_anti_aliasing_enabled'. if is_anti_aliasing_enabled then success := aapolygon_rgba_external (item, x_coordinates.array_address, y_coordinates.array_address, count, color.red, color.green, color.blue, color.alpha) if success < 0 then Error_handler.raise_warning (Error_handler.Em_error_put_polygon_filled, []) end end is_modified := True end set_pixel (a_x, a_y: INTEGER; a_color: EM_COLOR) is -- Set pixel at position `a_x' `a_y' to `a_color'. -- Note: For direct pixel access `Current' has to be locked using `lock'. -- Note: This feature uses direct memory access. So use `put_pixel' in normal cases. require surface_locked: is_locked x_coordinate_valid: 0 <= a_x and a_x < width y_coordinate_valid: 0 <= a_y and a_y < height a_color_not_void: a_color /= Void local color_value: INTEGER pointer_in_pixels: POINTER a_pixel_format: EM_PIXELFORMAT do a_pixel_format := pixel_format color_value := sdl_map_rgba_external (a_pixel_format.sdl_pixel_format_struct.item, a_color.red, a_color.green, a_color.blue, a_color.alpha) pointer_in_pixels := pixels + ((a_y * pitch) + (a_x * a_pixel_format.bytes_per_pixel)) pointer_in_pixels.memory_set (color_value, a_pixel_format.bytes_per_pixel) is_modified := True ensure -- TODO: this postcondition was violated, so I uncommented it -- pixel_set: pixel_value (a_x, a_y) = pixel_format.color_to_pixel_value (a_color) end set_pixel_value (a_x, a_y: INTEGER; a_value: INTEGER) is -- Set pixel at position `a_x' `a_y' to `a_value'. -- Note: For direct pixel access `Current' has to be locked using `lock'. -- Note: This feature uses direct memory access. So use `put_pixel' in normal cases. require surface_locked: is_locked x_coordinate_valid: 0 <= a_x and a_x < width y_coordinate_valid: 0 <= a_y and a_y < height local pointer_in_pixels: POINTER a_pixel_format: EM_PIXELFORMAT do a_pixel_format := pixel_format pointer_in_pixels := pixels + ((a_y * pitch) + (a_x * resolution)) pointer_in_pixels.memory_set (a_value, resolution) is_modified := True ensure -- TODO: this postcondition was violated, so I uncommented it -- pixel_set: pixel_value (a_x, a_y) = a_value end feature {NONE} -- Implementation internal_texture: GL_SURFACE_TEXTURE -- Texture of surface rect_: EM_BLITTING_RECTANGLE is -- This rect is used by blit and blit_part -- If every time blit or blit_part is called a -- new EM_BLITTING_RECTANGLE is allocated the garbage collector -- has to much to do (slows down the application) once create result.make (0,0,0,0) end destination_: EM_BLITTING_RECTANGLE is -- see rect_ once create result.make (0,0,0,0) end src_rect_: EM_RECTANGLE is -- Used for implementation of `draw_part', -- such that not every time a new rectangle -- needs to be created. once create Result.make_from_coordinates (0, 0, 0, 0) end dst_rect_: EM_RECTANGLE is -- Used for implementation of `draw_part', -- such that not every time a new rectangle -- needs to be created. once create Result.make_from_coordinates (0, 0, 0, 0) end clipping_: EM_BLITTING_RECTANGLE -- precreated rectangle, used for faster implementation of `clipping_rectangle'. line_segment_x_coords: EM_INTEGER_ARRAY is -- Array to pass wide line segment points to SDL. once create Result.make_new_unshared (4) end line_segment_y_coords: EM_INTEGER_ARRAY is -- Array to pass wide line segment points to SDL. once create Result.make_new_unshared (4) end pre_rotozoomed_surface: POINTER -- Pointer to surface before any rotating or zooming draw_polyline_internal (points: DS_LINEAR [EM_VECTOR_2D]; close: BOOLEAN) is -- Draw polyline going through `points' with `line_width' and `drawing_color'. -- If `close' is `True' ensure to close the polyline (polygon). require at_least_two_points: points.count >= 2 local cur_point, next_point: EM_VECTOR_2D cursor: DS_LINEAR_CURSOR [EM_VECTOR_2D] p1, p2: EM_VECTOR_2D -- segment points projected onto surface. p1x, p1y, p2x, p2y: DOUBLE left, right, upper, lower: DOUBLE clip_box: EM_RECTANGLE finished: BOOLEAN do -- Prepare for rough line clipping: -- Just clip to coordinate_area in world_coordinates plus a `line_width' border, -- (this is much easier, because possible without considering the line width) -- this rough clipping is sufficiant, since SDL does an exactly per pixel clipping, -- but anyway needed because SDL clipping alone can get very slow for large polygons -- and uncliped coordinates transformed to display coordinates could run into infinity -- (i.e. for very large polyline segments) which would lead to wrong (ugly) results. clip_box := coordinate_area left := clip_box.left_bound - line_width right := clip_box.right_bound + line_width upper := clip_box.upper_bound - line_width lower := clip_box.lower_bound + line_width -- Precreate clipping result points. create p1.make (0, 0) create p2.make (0, 0) -- Draw line segments for consecutive points cursor := points.new_cursor from cursor.start cur_point := cursor.item cursor.forth finished := cursor.after until finished loop if cursor.after and close then -- This is the last segment to just close the polyline. -- Restart from beginning and set finish to `True' finished := True cursor.start end next_point := cursor.item cursor.forth -- Do line clipping before projecting and drawing with line width. clip_line_segment (cur_point.x, cur_point.y, next_point.x, next_point.y, left, upper, right, lower, p1, p2) -- Only draw if different points. if p1.x /= p2.x or else p1.y /= p2.y then -- Project points to surface coordinates. p1 := device_point (p1) p2 := device_point (p2) p1x := p1.x p1y := p1.y p2x := p2.x p2y := p2.y put_wide_line_segment (p1x.floor, p1y.floor, p2x.floor, p2y.floor, device_line_width, drawing_color) -- Draw line points rounded, if enabled. if is_line_point_rounding_enabled and then device_line_width >= 2 then put_circle_filled (p1x.floor, p1y.floor, device_line_width // 2, drawing_color) if cursor. after then -- also for last point. put_circle_filled (p2x.floor, p2y.floor, device_line_width // 2, drawing_color) end end -- Go to next point. cur_point := next_point else cur_point := next_point end if cursor.after and not close then -- Finished (no closing of polyline needed). finished := True end end end clip_line_segment (fromx, fromy, tox, toy: DOUBLE; left, upper, right, lower: DOUBLE; res1, res2: EM_VECTOR_2D) is -- Calculate `res1' and `res2' as the two points needed to be drawed for -- line segment from point at `fromx' and `fromy' to point at `tox' and `toy' -- by clipping them at rectangle with given boundaries -- `left', `upper', `right', `lower'. -- Result is directly computed in `res1' and `res2'. require res1_not_void: res1 /= Void res2_not_void: res2 /= Void local from_region, to_region: INTEGER p, minp, maxp, dx, dy: DOUBLE do -- Calculate region code for `p1' if fromx < left then from_region := 1 elseif fromx > right then from_region := 2 end if fromy < upper then from_region := from_region + 4 elseif fromy > lower then from_region := from_region + 8 end -- Calculate region code for `p2' if tox < left then to_region := 1 elseif tox > right then to_region := 2 end if toy < upper then to_region := to_region | 4 elseif toy > lower then to_region := to_region | 8 end if from_region = 0 and then to_region = 0 then -- if both points are inside (code = 0) -- then no clipping is needed res1.make (fromx, fromy) res2.make (tox, toy) elseif from_region & to_region /= 0 then -- Both in the same half space --> nothing to be drawed. res1.make (res2.x, res2.y) else -- Otherwise clip with all 4 boundaries. -- Parameters to clip start at [0, 1]. minp := 0.0 maxp := 1.0 -- Precalculate `dx' and `dy'. dx := tox - fromx dy := toy - fromy -- Check intersection with `left_bound'. if (from_region & 1) /= (to_region & 1) then p := (left - fromx) / dx if p > minp and then p < maxp then -- Is entering or exiting? if fromx < left then minp := p else maxp := p end end end -- Check intersection with `right_bound'. if (from_region & 2) /= (to_region & 2) then p := (right - fromx) / dx if p > minp and then p < maxp then -- Is entering or exiting? if fromx > right then minp := p else maxp := p end end end -- Check intersection with `upper_bound'. -- (fromy < upper and then toy >= upper) or else (fromy > upper and then toy <= upper) if (from_region & 4) /= (to_region & 4) and then fromy /= upper then p := (upper - fromy) / dy if p > minp and then p < maxp then -- Is entering or exiting? if fromy < upper then minp := p else maxp := p end end end -- Check intersection with `lower_bound'. if (from_region & 8) /= (to_region & 8) and then fromy /= lower then p := (lower - fromy) / dy if p > minp and then p < maxp then -- Is entering or exiting? if fromy > lower then minp := p else maxp := p end end end -- Calculate cliped segment points. res1.make (fromx + minp * dx, fromy + minp * dy) res2.make (fromx + maxp * dx, fromy + maxp * dy) end end polygon_edge_clipping_point (fromx, fromy, tox, toy: DOUBLE; left, upper, right, lower: DOUBLE): EM_VECTOR_2D is -- Calculate the first point intersecting with boundaries `left', `upper', `right' or `lower' -- when going from `from_point' on a straight line to `to_point'. Needed for polygon clipping implementation. local intersection_param, p: DOUBLE px, py: DOUBLE from_region, to_region: INTEGER do -- Calculate region code for `from_point' if fromx < left then from_region := 1 elseif fromx > right then from_region := 2 end if fromy < upper then from_region := from_region + 4 elseif fromy > lower then from_region := from_region + 8 end -- Calculate region code for `to_point' if tox < left then to_region := 1 elseif tox > right then to_region := 2 end if toy < upper then to_region := to_region + 4 elseif toy > lower then to_region := to_region + 8 end -- Check if both points in same region --> no clipping needed. if from_region = to_region then Result := Void -- Otherwise check for clipping with each edge. else intersection_param := 2.0 -- Check intersection with `left_bound'. if (from_region & 1) /= (to_region & 1) and then fromx /= left then p := (left - fromx) / (tox - fromx) if p < intersection_param then intersection_param := p px := left py := fromy + intersection_param * (toy - fromy) end end -- Check intersection with `right_bound'. if (from_region & 2) /= (to_region & 2) and then fromx /= right then p := (right - fromx) / (tox - fromx) if p < intersection_param then intersection_param := p px := right py := fromy + intersection_param * (toy - fromy) end end -- Check intersection with `upper_bound'. -- (fromy < upper and then toy >= upper) or else (fromy > upper and then toy <= upper) if (from_region & 4) /= (to_region & 4) and then fromy /= upper then p := (upper - fromy) / (toy - fromy) if p < intersection_param then intersection_param := p px := fromx + intersection_param * (tox - fromx) py := upper end end -- Check intersection with `lower_bound'. if (from_region & 8) /= (to_region & 8) and then fromy /= lower then p := (lower - fromy) / (toy - fromy) if p < intersection_param then intersection_param := p px := fromx + intersection_param * (tox - fromx) py := lower end end -- Return `intersection_point', if any. if intersection_param <= 1.1 then create Result.make (px, py) end end end apply_rotozoom is -- Rotate and zoom `Current' according to `rotation_angle' and `zoom_factor's. local zoomed_surface: POINTER new_surface: POINTER antialiasing: INTEGER do -- First time store surface struct otherwise free it if pre_rotozoomed_surface = default_pointer then pre_rotozoomed_surface := item else sdl_free_surface_external (item) end -- Apply rotozoom if is_anti_aliasing_enabled then antialiasing := 1 end if zoom_factor_x = zoom_factor_y then new_surface := rotozoom_surface_external (pre_rotozoomed_surface, rotation_angle, zoom_factor_x, antialiasing) else -- TODO: Needs SDL_gfx 2.0.13 (also in EM_BITMAP_FACTORY) -- new_surface := rotozoom_surface_xy_external (pre_rotozoomed_surface, rotation_angle, zoom_factor_x, zoom_factor_y, antialiasing) zoomed_surface := zoom_surface_external (pre_rotozoomed_surface, zoom_factor_x, zoom_factor_y, antialiasing) new_surface := rotozoom_surface_external (zoomed_surface, rotation_angle, 1, antialiasing) sdl_free_surface_external (zoomed_surface) end if new_surface = Default_pointer then Error_handler.raise_error (Error_handler.Em_error_apply_rotozoom, [zoom_factor_x, zoom_factor_y, rotation_angle]) end -- Set `item' to `new_surface' item := new_surface is_modified := true if internal_texture /= Void then internal_texture.set_surface (Current) end -- TODO: check if this is all we have to do! -- Do we have to change any rectangles? bounding box? visible_area? ... end feature {NONE} -- Coordinate System Implementation visible_area: EM_RECTANGLE -- Rectangle that defines the coordinate system, -- coordinates inside this "user coordinate rectangle" -- are transformed into coordinates inside 'bounding_box' -- of `surface'. -- `coordinate_area' is calculated through `bounding_box' and `clipping_rectangle' of -- `surface' and `Current's `visible_area'. -- Transforming `coordinate_area' internaly changes `visible_area' -- and clipping `coordinate_area' internaly changes 'clipping_rectangle' -- of `surface'. feature {NONE} -- Implementation internal_clipping_rect: EM_BLITTING_RECTANGLE is -- EM_BLITTING_RECTANGLE needed to pass clipping area to `surface'. -- For not needing to allocate one for each EM_SURFACE once create Result.make (0, 0, 0, 0) end -- Following features are used by implementations of drawing primitives when they need INT-16-ARRAYs to pass coordinates to EM_SURFACE. -- (for performance reason, such that we don't need to create them each time) big_enough_x_coords_array (n: INTEGER): EM_INTEGER_ARRAY is -- Array containing at least `n' items -- that can be used by drawing primitives -- for passing x coordinates to `surface'. -- (for performance, no need to create an array each time) require n_positive: n >= 0 local ewg_array: EM_INTEGER_ARRAY do ewg_array := x_coords_ewg_array_cell.item if ewg_array.count < n then create ewg_array.make_new_unshared (2 * n) x_coords_ewg_array_cell.put (ewg_array) end Result := ewg_array ensure Result.count >= n end big_enough_y_coords_array (n: INTEGER): EM_INTEGER_ARRAY is -- Array containing at least `n' items -- that can be used by drawing primitives -- for passing y coordinates to `surface'. -- (for performance, no need to create an array each time) require n_positive: n >= 0 local ewg_array: EM_INTEGER_ARRAY do ewg_array := y_coords_ewg_array_cell.item if ewg_array.count < n then create ewg_array.make_new_unshared (2 * n) y_coords_ewg_array_cell.put (ewg_array) end Result := ewg_array ensure Result.count >= n end x_coords_ewg_array: EM_INTEGER_ARRAY is -- Array that can be used by drawing primitives -- for passing x coordinates to `surface'. -- (for performance, no need to create an array each time) do Result := x_coords_ewg_array_cell.item end y_coords_ewg_array: EM_INTEGER_ARRAY is -- Array that can be used by drawing primitives -- for passing y coordinates to `surface'. -- (for performance, no need to create an array each time) do Result := y_coords_ewg_array_cell.item end x_coords_ewg_array_cell: DS_CELL [EM_INTEGER_ARRAY] is -- Cell containing an array that can be used by drawing primitives -- for passing x coordinates to `surface'. -- (for performance, no need to create an array each time) local ewg_array: EM_INTEGER_ARRAY once create ewg_array.make_new_unshared (256) create Result.make (ewg_array) end y_coords_ewg_array_cell: DS_CELL [EM_INTEGER_ARRAY] is -- Cell containing an array that can be used by drawing primitives -- for passing y coordinates to `surface'. -- (for performance, no need to create an array each time) local ewg_array: EM_INTEGER_ARRAY once create ewg_array.make_new_unshared (256) create Result.make (ewg_array) end feature {EM_SURFACE, EM_BITMAP_FACTORY, EM_APPLICATION, GL_TEXTURE} -- Implementation item: POINTER -- Pointer to C surface value feature -- Removal dispose is -- Free external resources. do if item /= Default_pointer then sdl_free_surface_external (item) end Precursor end invariant drawing_color_not_void: drawing_color /= Void line_width_not_negative: line_width >= 0 exists: exists either_per_pixel_or_per_surface_alpha: is_alpha_transparency_enabled implies is_per_pixel_alpha_enabled xor is_per_surface_alpha_enabled end