-
-
Notifications
You must be signed in to change notification settings - Fork 656
.2869047221311500:9c5759a5394aa1b51101e64d032d75de_69e614bbf51efcfb0ecaa3bf.69e62868f51efcfb0ecaa543.69e62867d90ca3ecca53d17c:Trae CN.T(2026/4/20 21:21:44)Trae 4 #374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,167 @@ | ||||||||||||||||||
| // @ts-check | ||||||||||||||||||
| /* global $canvas_area, magnification */ | ||||||||||||||||||
| import { Handles } from "./Handles.js"; | ||||||||||||||||||
| import { OnCanvasObject } from "./OnCanvasObject.js"; | ||||||||||||||||||
| import { $G, E, make_canvas, make_css_cursor, to_canvas_coords } from "./helpers.js"; | ||||||||||||||||||
|
|
||||||||||||||||||
| class ReferenceImage extends OnCanvasObject { | ||||||||||||||||||
| /** | ||||||||||||||||||
| * @param {number} x | ||||||||||||||||||
| * @param {number} y | ||||||||||||||||||
| * @param {HTMLImageElement | HTMLCanvasElement | ImageData} image_source | ||||||||||||||||||
| * @param {number} [opacity=0.5] | ||||||||||||||||||
| */ | ||||||||||||||||||
| constructor(x, y, image_source, opacity = 0.5) { | ||||||||||||||||||
| const img_canvas = make_canvas(image_source); | ||||||||||||||||||
| super(x, y, img_canvas.width, img_canvas.height, false); | ||||||||||||||||||
|
|
||||||||||||||||||
| this.$el.addClass("reference-image"); | ||||||||||||||||||
| this._opacity = opacity; | ||||||||||||||||||
| this._visible = true; | ||||||||||||||||||
| this._original_canvas = img_canvas; | ||||||||||||||||||
| this.canvas = make_canvas(img_canvas); | ||||||||||||||||||
|
|
||||||||||||||||||
| this._update_canvas_opacity(); | ||||||||||||||||||
| this.$el.append(this.canvas); | ||||||||||||||||||
| this.position(); | ||||||||||||||||||
|
|
||||||||||||||||||
| this.$move_handle = $(E("div")).addClass("reference-move-handle"); | ||||||||||||||||||
| this.$move_handle.css({ | ||||||||||||||||||
| position: "absolute", | ||||||||||||||||||
| width: "20px", | ||||||||||||||||||
| height: "20px", | ||||||||||||||||||
| background: "#c0c0c0", | ||||||||||||||||||
| border: "2px outset #dfdfdf", | ||||||||||||||||||
| cursor: make_css_cursor("move", [8, 8], "move"), | ||||||||||||||||||
| touchAction: "none", | ||||||||||||||||||
| pointerEvents: "all", | ||||||||||||||||||
| zIndex: 1, | ||||||||||||||||||
| }); | ||||||||||||||||||
| this.$el.append(this.$move_handle); | ||||||||||||||||||
|
|
||||||||||||||||||
| this.handles = new Handles({ | ||||||||||||||||||
| $handles_container: this.$el, | ||||||||||||||||||
| $object_container: $canvas_area, | ||||||||||||||||||
| outset: 2, | ||||||||||||||||||
| get_rect: () => ({ x: this.x, y: this.y, width: this.width, height: this.height }), | ||||||||||||||||||
| set_rect: ({ x, y, width, height }) => { | ||||||||||||||||||
| this.x = x; | ||||||||||||||||||
| this.y = y; | ||||||||||||||||||
| this._resize(width, height); | ||||||||||||||||||
| this.position(); | ||||||||||||||||||
| }, | ||||||||||||||||||
| get_ghost_offset_left: () => parseFloat($canvas_area.css("padding-left")) + 1, | ||||||||||||||||||
| get_ghost_offset_top: () => parseFloat($canvas_area.css("padding-top")) + 1, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| let mox, moy; | ||||||||||||||||||
| const pointermove = (e) => { | ||||||||||||||||||
| const m = to_canvas_coords(e); | ||||||||||||||||||
| this.x = m.x - mox; | ||||||||||||||||||
| this.y = m.y - moy; | ||||||||||||||||||
| this.position(); | ||||||||||||||||||
| this._update_move_handle_position(); | ||||||||||||||||||
| }; | ||||||||||||||||||
| this.$move_handle.on("pointerdown", (e) => { | ||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||
| e.stopPropagation(); | ||||||||||||||||||
| const rect = this.canvas.getBoundingClientRect(); | ||||||||||||||||||
| const cx = e.clientX - rect.left; | ||||||||||||||||||
| const cy = e.clientY - rect.top; | ||||||||||||||||||
| mox = ~~(cx / rect.width * this.canvas.width); | ||||||||||||||||||
| moy = ~~(cy / rect.height * this.canvas.height); | ||||||||||||||||||
|
Comment on lines
+68
to
+72
|
||||||||||||||||||
| const rect = this.canvas.getBoundingClientRect(); | |
| const cx = e.clientX - rect.left; | |
| const cy = e.clientY - rect.top; | |
| mox = ~~(cx / rect.width * this.canvas.width); | |
| moy = ~~(cy / rect.height * this.canvas.height); | |
| const m = to_canvas_coords(e); | |
| mox = m.x - this.x; | |
| moy = m.y - this.y; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,13 @@ | ||
| // @ts-check | ||
| // eslint-disable-next-line no-unused-vars | ||
| /* global $thumbnail_window:writable, canvas_bounding_client_rect:writable, current_history_node:writable, file_format:writable, file_name:writable, helper_layer:writable, history_node_to_cancel_to:writable, magnification:writable, monochrome:writable, palette:writable, pointer:writable, return_to_magnification:writable, return_to_tools:writable, root_history_node:writable, saved:writable, selected_colors:writable, selected_tool:writable, selected_tools:writable, selection:writable, show_grid:writable, show_thumbnail:writable, system_file_handle:writable, textbox:writable, thumbnail_canvas:writable, tool_transparent_mode:writable, transparency:writable, undos:writable */ | ||
| /* global $thumbnail_window:writable, canvas_bounding_client_rect:writable, current_history_node:writable, file_format:writable, file_name:writable, helper_layer:writable, history_node_to_cancel_to:writable, magnification:writable, monochrome:writable, palette:writable, pointer:writable, reference_image:writable, return_to_magnification:writable, return_to_tools:writable, root_history_node:writable, saved:writable, selected_colors:writable, selected_tool:writable, selected_tools:writable, selection:writable, show_grid:writable, show_thumbnail:writable, system_file_handle:writable, textbox:writable, thumbnail_canvas:writable, tool_transparent_mode:writable, transparency:writable, undos:writable */ | ||
| /* global $canvas, $canvas_area, $colorbox, $status_text, $toolbox, $Window, AccessKeys, applyCSSProperties, decodeBMP, default_canvas_height, default_canvas_width, default_magnification, default_tool, enable_palette_loading_from_indexed_images, encodeBMP, localize, main_canvas, main_ctx, monochrome_palette, my_canvas_height, my_canvas_width, new_local_session, parseThemeFileString, pointer_active, pointers, polychrome_palette, redos, systemHooks, text_tool_font, update_fill_and_stroke_colors_and_lineWidth, UPNG, UTIF */ | ||
|
|
||
| import { $DialogWindow } from "./$ToolWindow.js"; | ||
| import { OnCanvasHelperLayer } from "./OnCanvasHelperLayer.js"; | ||
| import { OnCanvasSelection } from "./OnCanvasSelection.js"; | ||
| import { OnCanvasTextBox } from "./OnCanvasTextBox.js"; | ||
| import { ReferenceImage } from "./ReferenceImage.js"; | ||
| // import { localize } from "./app-localization.js"; | ||
| import { default_palette } from "./color-data.js"; | ||
| import { image_formats } from "./file-format-data.js"; | ||
|
|
@@ -1813,6 +1814,96 @@ async function choose_file_to_paste() { | |
| show_error_message(localize("This is not a valid bitmap file, or its format is not currently supported.")); | ||
| } | ||
|
|
||
| async function load_reference_image() { | ||
| const { file } = await systemHooks.showOpenFileDialog({ formats: image_formats }); | ||
| if (file.type.match(/^image|application\/pdf/)) { | ||
|
Comment on lines
+1817
to
+1819
|
||
| read_image_file(file, (error, info) => { | ||
| if (error) { | ||
|
Comment on lines
+1818
to
+1821
|
||
| show_file_format_errors({ as_image_error: error }); | ||
| return; | ||
| } | ||
| const img_or_canvas = info.image || make_canvas(info.image_data); | ||
|
|
||
| if (reference_image) { | ||
| reference_image.destroy(); | ||
| } | ||
|
|
||
| const default_opacity = 0.5; | ||
| reference_image = new ReferenceImage( | ||
| Math.max(0, Math.ceil($canvas_area.scrollLeft() / magnification)), | ||
| Math.max(0, Math.ceil($canvas_area.scrollTop() / magnification)), | ||
| img_or_canvas, | ||
| default_opacity | ||
| ); | ||
| }); | ||
| return; | ||
| } | ||
| show_error_message(localize("This is not a valid bitmap file, or its format is not currently supported.")); | ||
| } | ||
|
|
||
| function toggle_reference_image() { | ||
| if (reference_image) { | ||
| reference_image.toggle(); | ||
| } | ||
| } | ||
|
|
||
| function remove_reference_image() { | ||
| if (reference_image) { | ||
| reference_image.destroy(); | ||
| reference_image = null; | ||
| } | ||
| } | ||
|
|
||
| function set_reference_image_opacity(opacity) { | ||
| if (reference_image) { | ||
| reference_image.set_opacity(opacity); | ||
| } | ||
| } | ||
|
|
||
| function get_reference_image_opacity() { | ||
| return reference_image ? reference_image.get_opacity() : 0.5; | ||
| } | ||
|
|
||
| function is_reference_image_visible() { | ||
| return reference_image ? reference_image.is_visible() : false; | ||
| } | ||
|
|
||
| function has_reference_image() { | ||
| return reference_image != null; | ||
| } | ||
|
|
||
| function show_reference_image_opacity_window() { | ||
| if (!reference_image) { | ||
| return; | ||
| } | ||
|
|
||
| const $w = $DialogWindow(localize("Reference Image Opacity")); | ||
|
|
||
| $w.$main.append(` | ||
| <div style="padding: 10px;"> | ||
| <label for="reference-opacity-slider">${localize("Opacity:")}</label> | ||
| <input type="range" id="reference-opacity-slider" min="10" max="100" value="${Math.round(reference_image.get_opacity() * 100)}" | ||
| style="width: 200px; margin-left: 10px;" /> | ||
| <span id="reference-opacity-value" style="margin-left: 10px;">${Math.round(reference_image.get_opacity() * 100)}%</span> | ||
| </div> | ||
| `); | ||
|
|
||
| const $slider = $w.$main.find("#reference-opacity-slider"); | ||
| const $value = $w.$main.find("#reference-opacity-value"); | ||
|
|
||
| $slider.on("input", () => { | ||
| const opacity = parseInt($slider.val()?.toString() || "50", 10) / 100; | ||
| reference_image.set_opacity(opacity); | ||
| $value.text(`${Math.round(opacity * 100)}%`); | ||
| }); | ||
|
|
||
| $w.$Button(localize("OK"), () => { | ||
| $w.close(); | ||
| }); | ||
|
|
||
| $w.center(); | ||
| } | ||
|
|
||
| /** | ||
| * @param {HTMLImageElement | HTMLCanvasElement} img_or_canvas | ||
| */ | ||
|
|
@@ -4224,9 +4315,9 @@ export { | |
| $this_version_news, | ||
| apply_file_format_and_palette_info, are_you_sure, cancel, change_some_url_params, change_url_param, choose_file_to_paste, cleanup_bitmap_view, clear, confirm_overwrite_capability, delete_selection, deselect, detect_monochrome, | ||
| edit_copy, edit_cut, edit_paste, exit_fullscreen_if_ios, file_load_from_url, file_new, file_open, file_print, file_save, | ||
| file_save_as, getSelectionText, get_all_url_params, get_history_ancestors, get_tool_by_id, get_uris, get_url_param, go_to_history_node, handle_keyshortcuts, has_any_transparency, image_attributes, image_flip_and_rotate, image_invert_colors, image_stretch_and_skew, load_image_from_uri, load_theme_from_text, make_history_node, make_monochrome_palette, make_monochrome_pattern, make_opaque, make_or_update_undoable, make_stripe_pattern, meld_selection_into_canvas, | ||
| meld_textbox_into_canvas, open_from_file, open_from_image_info, paste, paste_image_from_file, please_enter_a_number, read_image_file, redo, render_canvas_view, render_history_as_gif, reset_canvas_and_history, reset_file, reset_selected_colors, resize_canvas_and_save_dimensions, resize_canvas_without_saving_dimensions, sanity_check_blob, save_as_prompt, save_selection_to_file, select_all, select_tool, select_tools, set_all_url_params, set_magnification, show_about_paint, show_convert_to_black_and_white, show_custom_zoom_window, show_document_history, show_error_message, show_file_format_errors, show_multi_user_setup_dialog, show_news, show_resource_load_error_message, switch_to_polychrome_palette, toggle_grid, | ||
| toggle_thumbnail, try_exec_command, undo, undoable, update_canvas_rect, update_css_classes_for_conditional_messages, update_disable_aa, update_from_saved_file, update_helper_layer, | ||
| file_save_as, getSelectionText, get_all_url_params, get_history_ancestors, get_reference_image_opacity, get_tool_by_id, get_uris, get_url_param, go_to_history_node, handle_keyshortcuts, has_any_transparency, has_reference_image, image_attributes, image_flip_and_rotate, image_invert_colors, image_stretch_and_skew, is_reference_image_visible, load_image_from_uri, load_reference_image, load_theme_from_text, make_history_node, make_monochrome_palette, make_monochrome_pattern, make_opaque, make_or_update_undoable, make_stripe_pattern, meld_selection_into_canvas, | ||
| meld_textbox_into_canvas, open_from_file, open_from_image_info, paste, paste_image_from_file, please_enter_a_number, read_image_file, redo, remove_reference_image, render_canvas_view, render_history_as_gif, reset_canvas_and_history, reset_file, reset_selected_colors, resize_canvas_and_save_dimensions, resize_canvas_without_saving_dimensions, sanity_check_blob, save_as_prompt, save_selection_to_file, select_all, select_tool, select_tools, set_all_url_params, set_magnification, set_reference_image_opacity, show_about_paint, show_convert_to_black_and_white, show_custom_zoom_window, show_document_history, show_error_message, show_file_format_errors, show_multi_user_setup_dialog, show_news, show_reference_image_opacity_window, show_resource_load_error_message, switch_to_polychrome_palette, toggle_grid, | ||
| toggle_reference_image, toggle_thumbnail, try_exec_command, undo, undoable, update_canvas_rect, update_css_classes_for_conditional_messages, update_disable_aa, update_from_saved_file, update_helper_layer, | ||
| update_helper_layer_immediately, update_magnified_canvas_size, update_title, view_bitmap, write_image_file | ||
| }; | ||
| // Temporary globals until all dependent code is converted to ES Modules | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ | |
| import { OnCanvasTextBox } from "./OnCanvasTextBox.js"; | ||
| import { show_edit_colors_window } from "./edit-colors.js"; | ||
| import { palette_formats } from "./file-format-data.js"; | ||
| import { are_you_sure, change_url_param, choose_file_to_paste, clear, delete_selection, deselect, edit_copy, edit_cut, edit_paste, file_load_from_url, file_new, file_open, file_print, file_save, file_save_as, image_attributes, image_flip_and_rotate, image_invert_colors, image_stretch_and_skew, redo, render_history_as_gif, sanity_check_blob, save_selection_to_file, select_all, set_magnification, show_about_paint, show_custom_zoom_window, show_document_history, show_file_format_errors, show_multi_user_setup_dialog, show_news, toggle_grid, toggle_thumbnail, undo, view_bitmap } from "./functions.js"; | ||
| import { are_you_sure, change_url_param, choose_file_to_paste, clear, delete_selection, deselect, edit_copy, edit_cut, edit_paste, file_load_from_url, file_new, file_open, file_print, file_save, file_save_as, has_reference_image, image_attributes, image_flip_and_rotate, image_invert_colors, image_stretch_and_skew, is_reference_image_visible, load_reference_image, redo, remove_reference_image, render_history_as_gif, sanity_check_blob, save_selection_to_file, select_all, set_magnification, show_about_paint, show_custom_zoom_window, show_document_history, show_file_format_errors, show_multi_user_setup_dialog, show_news, show_reference_image_opacity_window, toggle_grid, toggle_reference_image, toggle_thumbnail, undo, view_bitmap } from "./functions.js"; | ||
| import { show_help } from "./help.js"; | ||
| import { $G, get_rgba_from_color, is_discord_embed } from "./helpers.js"; | ||
| import { show_imgur_uploader } from "./imgur.js"; | ||
|
|
@@ -607,6 +607,59 @@ const menus = { | |
| }, | ||
| description: localize("Makes the application take up the entire screen."), | ||
| }, | ||
| MENU_DIVIDER, | ||
| { | ||
| emoji_icon: "📷", | ||
| label: localize("&Reference Image"), | ||
| submenu: [ | ||
| { | ||
| label: localize("&Load Reference Image") + "...", | ||
| speech_recognition: [ | ||
| "load reference image", "load reference", | ||
| "import reference image", "import reference", | ||
| "open reference image", "open reference", | ||
| ], | ||
| action: () => { load_reference_image(); }, | ||
| description: localize("Loads a reference image to trace over."), | ||
| }, | ||
| { | ||
| label: localize("&Show Reference Image"), | ||
| speech_recognition: [ | ||
| "toggle reference image", "toggle reference", | ||
| "show reference image", "show reference", | ||
| "hide reference image", "hide reference", | ||
| ], | ||
| enabled: () => has_reference_image(), | ||
| checkbox: { | ||
| toggle: () => { toggle_reference_image(); }, | ||
| check: () => is_reference_image_visible(), | ||
| }, | ||
| description: localize("Shows or hides the reference image."), | ||
| }, | ||
| { | ||
| label: localize("Reference Image &Opacity") + "...", | ||
| speech_recognition: [ | ||
| "change reference image opacity", "change reference opacity", | ||
| "adjust reference image opacity", "adjust reference opacity", | ||
| "reference image transparency", "reference transparency", | ||
| ], | ||
| enabled: () => has_reference_image(), | ||
| action: () => { show_reference_image_opacity_window(); }, | ||
| description: localize("Adjusts the opacity of the reference image."), | ||
| }, | ||
| { | ||
| label: localize("&Remove Reference Image"), | ||
| speech_recognition: [ | ||
| "remove reference image", "remove reference", | ||
| "delete reference image", "delete reference", | ||
| "clear reference image", "clear reference", | ||
| ], | ||
| enabled: () => has_reference_image(), | ||
| action: () => { remove_reference_image(); }, | ||
| description: localize("Removes the reference image."), | ||
| }, | ||
| ], | ||
| }, | ||
|
Comment on lines
+610
to
+662
|
||
| ], | ||
| [localize("&Image")]: [ | ||
| // @TODO: speech recognition: terms that apply to selection | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handlesregisters global$G.on("resize theme-load", update_handle)listeners but doesn’t provide a way to unregister them. Creating/destroying reference images repeatedly will accumulate window-level handlers (and this class also adds its own resize handler in addition toOnCanvasObject’s). Consider adding adestroy()method toHandlesthat removes its$Glisteners and calling it fromReferenceImage.destroy(). Also consider avoiding the extra resize listener by overridingposition()to also update the move-handle position, relying onOnCanvasObject’s resize handler.