diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs
--- a/crates/ui-egui/src/canvas.rs
+++ b/crates/ui-egui/src/canvas.rs
@@ -43,7 +43,7 @@ use area::{
 };
 #[cfg(test)]
 use bars::in_size_hint_zone;
-use bars::{Notice, notices, page_size_hint, quick_bar};
+use bars::{Notice, notices, page_size_hint, picked_image_bar, quick_bar};
 pub use cache::RasterMemory;
 pub(crate) use cache::{BASE_SIDE, TILE_THRESHOLD, texture_image, thumbnail_rows};
 #[cfg(test)]
@@ -2241,6 +2241,10 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
                     }
                 };
                 if let Some(image) = hit {
+                    if ui.button(tl!("Replace Image…")).clicked() {
+                        copy_image = Some(comments::CanvasAction::ReplaceImage(page, image));
+                        ui.close();
+                    }
                     if ui.button(tl!("Copy Image")).clicked() {
                         copy_image = Some(comments::CanvasAction::CopyImage(page, image));
                         ui.close();
@@ -2496,6 +2500,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
         Some(comments::CanvasAction::Properties(p, i)) => open_props = Some((p, i)),
         Some(comments::CanvasAction::CopyImage(p, i)) => image_action = Some(crate::edit_text_ui::ImageAction::Copy(p, i)),
         Some(comments::CanvasAction::CopyImageFile(p, i)) => image_action = Some(crate::edit_text_ui::ImageAction::CopyFile(p, i)),
+        Some(comments::CanvasAction::ReplaceImage(p, i)) => image_action = Some(crate::edit_text_ui::ImageAction::Replace(p, i)),
         None => {}
     }
     // Full screen: the click turns the page unless it did something else first. It waits out the
@@ -2647,6 +2652,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
     // Read mode hides the tools (#2646), and full screen is the page and nothing else (#1459);
     // the quick tools come back with the window.
     if app.mode != crate::Mode::Read && !app.full_screen {
+        picked_image_bar(app, ui);
         quick_bar(app, avail, ui);
     }
     if let Some((source, file, destination)) = remote_open {
diff --git a/crates/ui-egui/src/canvas/bars.rs b/crates/ui-egui/src/canvas/bars.rs
--- a/crates/ui-egui/src/canvas/bars.rs
+++ b/crates/ui-egui/src/canvas/bars.rs
@@ -485,3 +485,38 @@ pub(super) fn quick_bar(app: &mut PdfCraftApp, area: Rect, ui: &mut egui::Ui) {
             });
     });
 }
+
+/// Floating toolbar for a selected picture (`picked_image`).
+pub(super) fn picked_image_bar(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
+    let Some((view_idx, doc_id)) = app.active_ids() else { return };
+    let Some(picked) = app.views.get(view_idx).and_then(|v| v.picked_image) else { return };
+    let Some(doc) = app.session.get(doc_id) else { return };
+    let Some(xf) = app.views.get(view_idx).and_then(|v| v.page_xform(picked.page)) else { return };
+    if doc.info.pages.get(picked.page).is_none() || !picked.rect.iter().all(|v| v.is_finite()) {
+        return;
+    }
+    let r = xf.user_rect(&doc.info, picked.page, picked.rect.map(|v| v as f32));
+    let t = Tokens::get(ui.ctx());
+    let pos = pos2(r.center().x - 40.0, (r.top() - 36.0).max(10.0));
+    let mut replace = false;
+    let can_modify = doc.allows_modification();
+    egui::Area::new(egui::Id::unique("picked-image-bar")).order(egui::Order::Foreground).fixed_pos(pos).show(ui.ctx(), |ui| {
+        egui::Frame::NONE
+            .fill(t.card)
+            .stroke(Stroke::new(1.0, t.border))
+            .corner_radius(CornerRadius::same(8))
+            .shadow(egui::Shadow { offset: [0, 2], blur: 8, spread: 0, color: Color32::from_black_alpha(if t.dark() { 80 } else { 20 }) })
+            .inner_margin(egui::Margin::same(4))
+            .show(ui, |ui| {
+                ui.horizontal(|ui| {
+                    ui.spacing_mut().item_spacing.x = 2.0;
+                    if ui.add_enabled_ui(can_modify, |ui| icons::button(ui, "replace", 24.0, false, tl!("Replace image…"))).inner.clicked() {
+                        replace = true;
+                    }
+                });
+            });
+    });
+    if replace {
+        app.replace_image_dialog(picked.page, picked.index);
+    }
+}
diff --git a/crates/ui-egui/src/tools/comments/menu.rs b/crates/ui-egui/src/tools/comments/menu.rs
--- a/crates/ui-egui/src/tools/comments/menu.rs
+++ b/crates/ui-egui/src/tools/comments/menu.rs
@@ -137,6 +137,8 @@ pub enum CanvasAction {
     CopyImage(usize, usize),
     /// Copy Image in Original Format: the same image as a file (JPEG as stored, else PNG).
     CopyImageFile(usize, usize),
+    /// Replace image: image (page, index in `page_images`) with a chosen image file.
+    ReplaceImage(usize, usize),
 }
 
 /// How many custom colours the "Recent colours" row keeps.
diff --git a/crates/ui-egui/src/tools/content_ui.rs b/crates/ui-egui/src/tools/content_ui.rs
--- a/crates/ui-egui/src/tools/content_ui.rs
+++ b/crates/ui-egui/src/tools/content_ui.rs
@@ -867,7 +867,16 @@ impl crate::PdfCraftApp {
     pub fn replace_image_dialog(&mut self, page: usize, index: usize) {
         #[cfg(not(target_arch = "wasm32"))]
         self.pick_image(tl!("Replace image"), move |app, name, bytes| {
-            app.apply_edit(Edit::ReplaceImage { page, index, name, bytes: std::sync::Arc::new(bytes) });
+            let Some((_, id)) = app.active_ids() else { return };
+            let is_added = app.session.get(id).is_some_and(|d| {
+                d.added().iter().filter(|a| a.page == page).nth(index).is_some_and(|a| matches!(a.content, pdfcraft_engine::AddedContent::Image(_)))
+            });
+            let edit = if is_added {
+                Edit::ReplaceImage { page, index, name, bytes: std::sync::Arc::new(bytes) }
+            } else {
+                Edit::EditPageImage { page, index, change: pdfcraft_engine::ImageEdit::Replace { name, bytes: std::sync::Arc::new(bytes) } }
+            };
+            app.apply_edit(edit);
         });
         #[cfg(target_arch = "wasm32")]
         {
diff --git a/crates/ui-egui/tests/it/viewer.rs b/crates/ui-egui/tests/it/viewer.rs
--- a/crates/ui-egui/tests/it/viewer.rs
+++ b/crates/ui-egui/tests/it/viewer.rs
@@ -1830,6 +1830,42 @@ fn right_click_on_a_picture_copies_it_at_full_size() {
     assert_eq!(rgba, px.into_raw(), "the image's own pixels, not a rendering of the page");
 }
 
+#[test]
+fn right_click_and_floating_bar_offer_replace_image() {
+    let (_, mut h) = picture_harness();
+    let i = h.state().active.unwrap();
+    let at = h.state().views[i].page_screen_rect(0).expect("on screen").center();
+    // 1. Right-click context menu offers "Replace Image…"
+    h.hover_at(at);
+    h.run_steps(1);
+    h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Secondary, pressed: true, modifiers: Default::default() });
+    h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Secondary, pressed: false, modifiers: Default::default() });
+    h.run_steps(2);
+    h.get_by_label("Replace Image…");
+    h.event(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() });
+    h.run_steps(2);
+
+    // 2. Left-click selects the picture and floating bar offers "Replace image…"
+    h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() });
+    h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() });
+    h.run_steps(2);
+    assert!(h.state().views[i].picked_image.is_some(), "picked with the Select tool");
+    h.get_by_label("Replace image…");
+
+    // 3. Applying replacement updates image data
+    let png = {
+        let mut buf = std::io::Cursor::new(Vec::new());
+        image::RgbaImage::from_pixel(10, 10, image::Rgba([0, 255, 0, 255])).write_to(&mut buf, image::ImageFormat::Png).unwrap();
+        buf.into_inner()
+    };
+    let replace = pdfcraft_engine::Edit::EditPageImage {
+        page: 0,
+        index: 0,
+        change: pdfcraft_engine::ImageEdit::Replace { name: "green.png".into(), bytes: std::sync::Arc::new(png) },
+    };
+    assert!(h.state_mut().apply_edit(replace));
+}
+
 /// A picture drawn through grouped Form artwork with a caption over it (as Word, InDesign and
 /// scanners with OCR write them): one click still selects it, and ⌘C copies the picture.
 #[test]
