Coverage for paintmaskanimator/display_shrink.py: 86%

39 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""縮小表示のしかた(Issue #46)。 

2 

3キャンバス全体が見えるところまで縮めると、線が目に見えて荒れる。表示は常に 

4ニアレストネイバー(``FastTransformation``)で縮めており、1画素の線は縮小率 

5しだいで拾われたり落ちたりするためで、線が途切れて見えるのも同じ理由による。 

6 

7ここでは「線を残す」縮小を用意する。半分ずつ縮めながら、2×2 のうち最も不透明 

8(同じ不透明さなら最も暗い)画素を残す。細い線が消えず、太りもしない。ソース 

9の画素は触らず、表示のためだけに使う。 

10""" 

11import numpy as np 

12from PySide6.QtGui import QImage 

13 

14from . import imaging 

15 

16#: 設定値。``nearest`` が従来どおりの表示。 

17MODE_NEAREST = "nearest" 

18MODE_KEEP_LINES = "keep_lines" 

19MODES = (MODE_NEAREST, MODE_KEEP_LINES) 

20 

21 

22def _winners(a, b): 

23 """2枚の RGBA 配列から、画素ごとに「線らしい方」を選ぶマスクを返す。 

24 

25 不透明な方を優先し、同じ不透明さなら暗い方(線は背景より暗い)を選ぶ。 

26 """ 

27 alpha_a = a[:, :, 3].astype(np.int16) 

28 alpha_b = b[:, :, 3].astype(np.int16) 

29 luma_a = a[:, :, :3].astype(np.int16).sum(axis=2) 

30 luma_b = b[:, :, :3].astype(np.int16).sum(axis=2) 

31 return (alpha_a > alpha_b) | ((alpha_a == alpha_b) & (luma_a <= luma_b)) 

32 

33 

34def _pick(a, b): 

35 keep = _winners(a, b)[:, :, None] 

36 return np.where(keep, a, b) 

37 

38 

39def halve_keeping_lines(rgba): 

40 """RGBA 配列を縦横半分にする。2×2 のうち線らしい画素を残す。""" 

41 height, width = rgba.shape[:2] 

42 height -= height % 2 

43 width -= width % 2 

44 if height < 2 or width < 2: 44 ↛ 45line 44 didn't jump to line 45 because the condition on line 44 was never true

45 return rgba 

46 block = rgba[:height, :width] 

47 return _pick( 

48 _pick(block[0::2, 0::2], block[0::2, 1::2]), 

49 _pick(block[1::2, 0::2], block[1::2, 1::2]), 

50 ) 

51 

52 

53def shrink_keeping_lines(image: QImage, width: int, height: int) -> QImage: 

54 """``image`` を ``width`` × ``height`` へ、線を残したまま縮める。 

55 

56 半分ずつ縮めて目標の2倍以内まで寄せ、最後の端数だけニアレストで合わせる。 

57 拡大や等倍は呼び出し側で扱う(ここでは縮小だけを受け持つ)。 

58 """ 

59 width = max(1, int(width)) 

60 height = max(1, int(height)) 

61 if image.width() <= width and image.height() <= height: 

62 return image 

63 

64 rgba = imaging.qimage_rgba_array(image) 

65 while rgba.shape[0] >= height * 2 and rgba.shape[1] >= width * 2: 

66 halved = halve_keeping_lines(rgba) 

67 if halved.shape == rgba.shape: 67 ↛ 68line 67 didn't jump to line 68 because the condition on line 67 was never true

68 break 

69 rgba = halved 

70 

71 reduced = imaging.rgba_array_to_qimage(np.ascontiguousarray(rgba)) 

72 if (reduced.width(), reduced.height()) == (width, height): 72 ↛ 74line 72 didn't jump to line 74 because the condition on line 72 was always true

73 return reduced 

74 from PySide6.QtCore import Qt 

75 

76 return reduced.scaled( 

77 width, 

78 height, 

79 Qt.AspectRatioMode.IgnoreAspectRatio, 

80 Qt.TransformationMode.FastTransformation, 

81 )