Skip to content

Commit 6ad120e

Browse files
committed
Merge branch 'master' into 4.7
# Conflicts: # weasis-dicom/weasis-dicom-viewer2d/src/main/java/org/weasis/dicom/viewer2d/InfoLayer.java # weasis-parent/pom.xml
2 parents 55058ce + 36a675a commit 6ad120e

File tree

20 files changed

+1071
-641
lines changed

20 files changed

+1071
-641
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
# Changelog
22

3+
## [v4.6.5](https://github.com/nroduit/Weasis/tree/v4.6.5) (2025-10-05)
4+
5+
[Full Changelog](https://github.com/nroduit/Weasis/compare/v4.6.4...v4.6.5)
6+
7+
**Implemented enhancements:**
8+
9+
- New behavior of 4D DICOM series splitting [\#739](https://github.com/nroduit/Weasis/issues/739)
10+
11+
**Fixed bugs:**
12+
13+
- Weasis 4.6.3 vs. 4.6.4 - Update = Cannot read this media! [\#740](https://github.com/nroduit/Weasis/issues/740)
14+
15+
## [v4.6.4](https://github.com/nroduit/Weasis/tree/v4.6.4) (2025-10-03)
16+
17+
[Full Changelog](https://github.com/nroduit/Weasis/compare/v4.6.3...v4.6.4)
18+
19+
**Implemented enhancements:**
20+
21+
- Update to dcm4che 5.34.1 [\#738](https://github.com/nroduit/Weasis/issues/738)
22+
- Update to OpenCV 4.12.1 [\#737](https://github.com/nroduit/Weasis/issues/737)
23+
- Improve the display of interpolated curves in DICOM presentation states [\#734](https://github.com/nroduit/Weasis/issues/734)
24+
- Enable direct series scrolling in multi-view configuration without selection [\#729](https://github.com/nroduit/Weasis/issues/729)
25+
- Application freezes when using volume rendering on certain GPU configurations [\#723](https://github.com/nroduit/Weasis/issues/723)
26+
- MPR: Image orientation correction for non-standard patient positioning using 3D matrix transformations [\#716](https://github.com/nroduit/Weasis/issues/716)
27+
- Gantry tilt correction using backward mapping and trilinear interpolation [\#715](https://github.com/nroduit/Weasis/issues/715)
28+
- JPEG-XL Support [\#658](https://github.com/nroduit/Weasis/issues/658)
29+
30+
**Fixed bugs:**
31+
32+
- Dotted artifacts in MPR rendering [\#731](https://github.com/nroduit/Weasis/issues/731)
33+
- Images was cut in MPR view in v4.6 comparing to v4.5 [\#730](https://github.com/nroduit/Weasis/issues/730)
34+
- Image info on 3D viewer. [\#728](https://github.com/nroduit/Weasis/issues/728)
35+
- Text annotation encoding issue when saving annotations in DICOM Presentation State [\#725](https://github.com/nroduit/Weasis/issues/725)
36+
- Font size of pixel graphic tool and text annotation does not follow preference settings [\#724](https://github.com/nroduit/Weasis/issues/724)
37+
- Cursor icon not displaying correctly for current action and drawing [\#722](https://github.com/nroduit/Weasis/issues/722)
38+
- Actual pixel size tools fail to work in MPR mode [\#717](https://github.com/nroduit/Weasis/issues/717)
39+
40+
**Security fixes:**
41+
42+
- Fix CVE-2025-53644 in OpenCV \(jpeg2000 decoder\) [\#736](https://github.com/nroduit/Weasis/issues/736)
43+
- Code Injection via Apache felix plugin - CVE-2025-61585 [\#735](https://github.com/nroduit/Weasis/issues/735)
44+
345
## [v4.6.3](https://github.com/nroduit/Weasis/tree/v4.6.3) (2025-08-15)
446

547
[Full Changelog](https://github.com/nroduit/Weasis/compare/v4.6.2...v4.6.3)

snap/snapcraft.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: weasis
22
title: Weasis
33
base: core24
4-
version: '4.6.3'
4+
version: '4.6.5'
55
summary: A free/libre/open medical DICOM viewer
66
description: |
77
Weasis is a multipurpose standalone and web-based DICOM viewer with
@@ -82,8 +82,8 @@ parts:
8282
- libstdc++6
8383
- libgcc1
8484
#source: weasis-distributions/target/native-dist/weasis-native.zip
85-
source: https://github.com/nroduit/Weasis/releases/download/v4.6.3/weasis-native.zip
86-
source-checksum: sha256/01568d7a4075458d132991db6416601748d2ee1bc6d9983368919674fc9746dc
85+
source: https://github.com/nroduit/Weasis/releases/download/v4.6.5/weasis-native.zip
86+
source-checksum: sha256/5327d29b2fae1ef66c648be8c08d9bee9c010dc2dde3b71888a3f2c6a83e0d9f
8787
organize:
8888
'*': weasis-native/
8989
override-stage: 'true'

weasis-core/src/main/java/org/weasis/core/internal/cv/NativeOpenCVCodec.java

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,31 +48,15 @@ public class NativeOpenCVCodec implements Codec<ImageElement> {
4848
"image/jp2", // NON-NLS
4949
"image/jp2k", // NON-NLS
5050
"image/j2k", // NON-NLS
51-
"image/j2c" // NON-NLS
51+
"image/j2c", // NON-NLS
52+
"image/jxl" // NON-NLS
5253
};
5354
private static final String[] readerFileSuffixes = {
54-
"bm",
55-
"bmp",
56-
"dib",
57-
"jpeg",
58-
"jpg",
59-
"jpe",
60-
"png",
61-
"x-png",
62-
"pbm",
63-
"pgm",
64-
"ppm",
65-
"pxm", // NON-NLS
66-
"pnm",
67-
"ras",
68-
"rast", // NON-NLS
69-
"tiff",
70-
"tif",
71-
"hdr",
72-
"jp2",
73-
"jp2k", // NON-NLS
74-
"j2k",
75-
"j2c" // NON-NLS
55+
"bm", "bmp", "dib", "jpeg", "jpg", "jpe", "png", "x-png", "pbm", "pgm", "ppm", "pxm", // NON-NLS
56+
"pnm", "ras", "rast", // NON-NLS
57+
"tiff", "tif", "hdr", "jp2", "jp2k", // NON-NLS
58+
"j2k", "j2c", // NON-NLS
59+
"jxl"
7660
};
7761

7862
private static final String[] writerMIMETypes = {};

weasis-core/src/main/java/org/weasis/core/ui/editor/image/FocusHandler.java

Lines changed: 140 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,143 +14,199 @@
1414
import java.awt.event.MouseEvent;
1515
import java.awt.event.MouseWheelEvent;
1616
import java.awt.geom.Point2D;
17-
import java.util.Objects;
1817
import java.util.Optional;
1918
import org.opencv.core.Point3;
2019
import org.weasis.core.Messages;
21-
import org.weasis.core.api.gui.util.ActionState;
2220
import org.weasis.core.api.gui.util.Feature;
2321
import org.weasis.core.api.gui.util.MouseActionAdapter;
2422
import org.weasis.core.api.gui.util.WinUtil;
2523
import org.weasis.core.api.media.data.ImageElement;
2624
import org.weasis.core.ui.model.layer.LayerAnnotation;
25+
import org.weasis.core.ui.model.layer.LayerItem;
2726
import org.weasis.core.util.StringUtil;
2827

29-
public class FocusHandler<E extends ImageElement> extends MouseActionAdapter {
28+
/**
29+
* Handles focus and mouse interactions for image viewer canvas. Manages view selection, button
30+
* interactions, and pixel information display.
31+
*
32+
* @param <E> the type of image element
33+
*/
34+
public final class FocusHandler<E extends ImageElement> extends MouseActionAdapter {
35+
private static final long UPDATE_INTERVAL_MS = 50;
36+
3037
private final ViewCanvas<E> viewCanvas;
38+
private long lastUpdateTime;
3139

3240
public FocusHandler(ViewCanvas<E> viewCanvas) {
3341
this.viewCanvas = viewCanvas;
3442
}
3543

3644
@Override
3745
public void mousePressed(MouseEvent evt) {
38-
ImageViewerPlugin<E> pane = viewCanvas.getEventManager().getSelectedView2dContainer();
39-
if (Objects.isNull(pane)) {
46+
var pane = viewCanvas.getEventManager().getSelectedView2dContainer();
47+
if (pane == null) {
4048
return;
4149
}
4250

43-
ViewButton selectedButton = null;
51+
var selectedButton = findViewButtonAt(evt.getPoint());
4452
// Do select the view when pressing on a view button
45-
for (ViewButton b : viewCanvas.getViewButtons()) {
46-
if (b.isVisible() && b.contains(evt.getPoint())) {
47-
selectedButton = b;
48-
break;
49-
}
50-
}
51-
52-
if (evt.getClickCount() == 2 && selectedButton == null) {
53+
if (evt.getClickCount() == 2 && selectedButton.isEmpty()) {
5354
pane.maximizedSelectedImagePane(viewCanvas, evt);
5455
return;
5556
}
5657

57-
if (pane.isContainingView(viewCanvas) && pane.getSelectedImagePane() != viewCanvas) {
58-
// register all actions of the EventManager with this view waiting the focus gained in some
59-
// cases is not
60-
// enough, because others mouseListeners are triggered before the focus event (that means
61-
// before
62-
// registering the view in the EventManager)
63-
pane.setSelectedImagePane(viewCanvas);
64-
}
65-
// request the focus even it is the same pane selected
58+
selectViewIfNeeded(pane);
6659
viewCanvas.getJComponent().requestFocusInWindow();
6760

68-
// Do select the view when pressing on a view button
69-
if (selectedButton != null) {
70-
viewCanvas.getJComponent().setCursor(DefaultView2d.DEFAULT_CURSOR);
71-
evt.consume();
72-
selectedButton.showPopup(evt.getComponent(), evt.getX(), evt.getY());
61+
if (selectedButton.isPresent()) {
62+
handleViewButtonClick(evt, selectedButton.get());
7363
return;
7464
}
7565

76-
Optional<Feature<? extends ActionState>> action =
77-
viewCanvas.getEventManager().getMouseAction(evt.getModifiersEx());
78-
viewCanvas
79-
.getJComponent()
80-
.setCursor(action.isPresent() ? action.get().getCursor() : DefaultView2d.DEFAULT_CURSOR);
66+
updateCursorForMouseAction(evt);
8167
}
8268

8369
@Override
8470
public void mouseWheelMoved(MouseWheelEvent e) {
8571
ImageViewerEventManager<E> eventManager = viewCanvas.getEventManager();
86-
if (eventManager != null) {
87-
ImageViewerPlugin<E> container =
88-
WinUtil.getParentOfClass(viewCanvas.getJComponent(), ImageViewerPlugin.class);
89-
if (container != null) {
90-
if (!container.equals(eventManager.getSelectedView2dContainer())) {
91-
eventManager.setSelectedView2dContainer(container);
92-
}
93-
if (!container.getSelectedImagePane().equals(viewCanvas)) {
94-
container.setSelectedImagePane(viewCanvas);
95-
}
96-
}
72+
if (eventManager == null) {
73+
return;
74+
}
75+
ImageViewerPlugin<E> container =
76+
WinUtil.getParentOfClass(viewCanvas.getJComponent(), ImageViewerPlugin.class);
77+
if (container == null) {
78+
return;
79+
}
80+
81+
if (!container.equals(eventManager.getSelectedView2dContainer())) {
82+
eventManager.setSelectedView2dContainer(container);
83+
}
84+
if (!container.getSelectedImagePane().equals(viewCanvas)) {
85+
container.setSelectedImagePane(viewCanvas);
9786
}
9887
}
9988

10089
@Override
10190
public void mouseMoved(MouseEvent evt) {
10291
showPixelInfos(evt);
103-
for (ViewButton b : viewCanvas.getViewButtons()) {
104-
if (b.isVisible()) {
105-
boolean hover = b.contains(evt.getPoint());
106-
if (hover != b.isHover()) {
107-
b.setHover(hover);
108-
viewCanvas.getJComponent().repaint();
109-
}
110-
}
111-
}
92+
updateViewButtonsHoverState(evt.getPoint());
11293
}
11394

11495
@Override
11596
public void mouseReleased(MouseEvent evt) {
11697
viewCanvas.getJComponent().setCursor(DefaultView2d.DEFAULT_CURSOR);
117-
for (ViewButton b : viewCanvas.getViewButtons()) {
118-
if (b.isVisible() && b.contains(evt.getPoint())) {
119-
evt.consume();
120-
break;
121-
}
98+
findViewButtonAt(evt.getPoint()).ifPresent(_ -> evt.consume());
99+
}
100+
101+
private Optional<ViewButton> findViewButtonAt(Point point) {
102+
return viewCanvas.getViewButtons().stream()
103+
.filter(ViewButton::isVisible)
104+
.filter(button -> button.contains(point))
105+
.findFirst();
106+
}
107+
108+
private void selectViewIfNeeded(ImageViewerPlugin<E> pane) {
109+
if (pane.isContainingView(viewCanvas) && pane.getSelectedImagePane() != viewCanvas) {
110+
// Register all EventManager actions immediately with this view. Waiting for focus gain
111+
// is not enough since other MouseListeners may trigger before the focus event occurs,
112+
// resulting in the view not yet being registered in the EventManager.
113+
pane.setSelectedImagePane(viewCanvas);
122114
}
123115
}
124116

125-
protected void showPixelInfos(MouseEvent mouseevent) {
126-
LayerAnnotation infoLayer = viewCanvas.getInfoLayer();
127-
if (infoLayer != null) {
128-
Point2D pModel =
129-
viewCanvas.getImageCoordinatesFromMouse(mouseevent.getX(), mouseevent.getY());
130-
PixelInfo pixelInfo =
131-
viewCanvas.getPixelInfo(
132-
new Point((int) Math.floor(pModel.getX()), (int) Math.floor(pModel.getY())));
133-
if (pixelInfo == null) {
134-
return;
135-
}
136-
Rectangle oldBound = infoLayer.getPixelInfoBound();
117+
private void handleViewButtonClick(MouseEvent evt, ViewButton button) {
118+
viewCanvas.getJComponent().setCursor(DefaultView2d.DEFAULT_CURSOR);
119+
evt.consume(); // Consume event to not select the view
120+
button.showPopup(evt.getComponent(), evt.getX(), evt.getY());
121+
}
122+
123+
private void updateCursorForMouseAction(MouseEvent evt) {
124+
var action = viewCanvas.getEventManager().getMouseAction(evt.getModifiersEx());
125+
var cursor = action.map(Feature::getCursor).orElse(DefaultView2d.DEFAULT_CURSOR);
126+
viewCanvas.getJComponent().setCursor(cursor);
127+
}
128+
129+
private void updateViewButtonsHoverState(Point mousePoint) {
130+
viewCanvas.getViewButtons().stream()
131+
.filter(ViewButton::isVisible)
132+
.forEach(
133+
button -> {
134+
boolean hover = button.contains(mousePoint);
135+
if (hover != button.isHover()) {
136+
button.setHover(hover);
137+
viewCanvas.getJComponent().repaint();
138+
}
139+
});
140+
}
141+
142+
private void showPixelInfos(MouseEvent mouseevent) {
143+
var infoLayer = viewCanvas.getInfoLayer();
144+
if (!isPixelInfoVisible(infoLayer)) {
145+
return;
146+
}
147+
148+
if (!shouldUpdatePixelInfo()) {
149+
return;
150+
}
151+
152+
var pixelInfo = computePixelInfo(mouseevent);
153+
if (pixelInfo == null) {
154+
return;
155+
}
156+
updatePixelInfoDisplay(pixelInfo);
157+
}
158+
159+
private boolean isPixelInfoVisible(LayerAnnotation infoLayer) {
160+
return infoLayer != null
161+
&& infoLayer.getVisible()
162+
&& infoLayer.getDisplayPreferences(LayerItem.PIXEL)
163+
&& !infoLayer.getDisplayPreferences(LayerItem.MIN_ANNOTATIONS);
164+
}
165+
166+
private boolean shouldUpdatePixelInfo() {
167+
long currentTime = System.currentTimeMillis();
168+
if (currentTime - lastUpdateTime < UPDATE_INTERVAL_MS) {
169+
return false;
170+
}
171+
lastUpdateTime = currentTime;
172+
return true;
173+
}
174+
175+
private PixelInfo computePixelInfo(MouseEvent mouseevent) {
176+
Point2D pModel = viewCanvas.getImageCoordinatesFromMouse(mouseevent.getX(), mouseevent.getY());
177+
var pixelInfo =
178+
viewCanvas.getPixelInfo(
179+
new Point((int) Math.floor(pModel.getX()), (int) Math.floor(pModel.getY())));
180+
if (pixelInfo != null) {
137181
Point3 point3d =
138182
viewCanvas.getVolumeCoordinatesFromMouse(mouseevent.getX(), mouseevent.getY());
139183
pixelInfo.setPosition3d(point3d);
140-
oldBound.width =
141-
Math.max(
142-
oldBound.width,
143-
viewCanvas
144-
.getJComponent()
145-
.getGraphics()
146-
.getFontMetrics()
147-
.stringWidth(
148-
Messages.getString("DefaultView2d.pix")
149-
+ StringUtil.COLON_AND_SPACE
150-
+ pixelInfo)
151-
+ 4);
152-
infoLayer.setPixelInfo(pixelInfo);
153-
viewCanvas.getJComponent().repaint(oldBound);
154184
}
185+
186+
return pixelInfo;
187+
}
188+
189+
private void updatePixelInfoDisplay(PixelInfo pixelInfo) {
190+
var infoLayer = viewCanvas.getInfoLayer();
191+
Rectangle oldBound = infoLayer.getPixelInfoBound();
192+
int textWidth = calculatePixelInfoTextWidth(pixelInfo);
193+
oldBound.width = Math.max(oldBound.width, textWidth + 4);
194+
195+
infoLayer.setPixelInfo(pixelInfo);
196+
viewCanvas.getJComponent().repaint(oldBound);
197+
}
198+
199+
private int calculatePixelInfoTextWidth(PixelInfo pixelInfo) {
200+
String text =
201+
Messages.getString("DefaultView2d.pix")
202+
+ StringUtil.COLON_AND_SPACE
203+
+ pixelInfo.getPixelValueText()
204+
+ " - "
205+
+ pixelInfo.getPixelPositionText();
206+
207+
return viewCanvas
208+
.getJComponent()
209+
.getFontMetrics(viewCanvas.getJComponent().getFont())
210+
.stringWidth(text);
155211
}
156212
}

0 commit comments

Comments
 (0)