diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5abfba7..392334e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,7 +12,8 @@
"kernel", "0x10", "0x10",
"rootfs", "0x40", "0xDD",
"dtb", "0xCC", "0xCC",
- "-o", "doc/example/report.md"
+ "-o", "doc/example/report.md",
+ "-s", "3"
],
"justMyCode": false
}
diff --git a/doc/example/report.md b/doc/example/report.md
index cc1e074..ff16297 100644
--- a/doc/example/report.md
+++ b/doc/example/report.md
@@ -1,8 +1,8 @@

|name|origin|size|remaining|collisions
|:-|:-|:-|:-|:-|
-|kernel|0x10|0x50|-0x10|{'rootfs': '0x50'}|
-|uboot|0xD0|0x50|-0x10|{'uboot-scr': '0x110'}|
-|rootfs|0x50|0x30|0x10|{'kernel': '0x50'}|
-|dtb|0x90|0x30|0x10|{}|
-|uboot-scr|0x110|0x30|0x50|{'uboot': '0x110'}|
+|kernel|0x10|0x50|-0x10|{'rootfs': '0x50'}|
+|uboot|0xD0|0x50|-0x10|{'uboot-scr': '0x110'}|
+|rootfs|0x50|0x30|0x10|{'kernel': '0x50'}|
+|dtb|0x90|0x30|0x10|{}|
+|uboot-scr|0x110|0x30|0x50|{'uboot': '0x110'}|
diff --git a/doc/example/report.png b/doc/example/report.png
index 4e11e46..aef8044 100644
Binary files a/doc/example/report.png and b/doc/example/report.png differ
diff --git a/mm/diagram.py b/mm/diagram.py
index e92a0db..49543c6 100644
--- a/mm/diagram.py
+++ b/mm/diagram.py
@@ -25,21 +25,29 @@
@typeguard.typechecked
class MemoryMap:
- height: int = 400
- """height of the diagram image"""
- width: int = 400
- """width of the diagram image"""
- bgcolour = "oldlace"
-
def __init__(self):
- self._legend_width = 100
- """width of the area used for text annotations/legend"""
+ self.bgcolour = "oldlace"
+
+ self.height: int = 400
+ """height of the diagram image"""
+ self.width: int = 400
+ """width of the diagram image"""
+
+ self.default_region_text_size: int = 12
+ self.fixed_legend_text_size = 12
+
self._region_list = None
"""List of region objects"""
# create a list of region objects populated with input data
self._region_list = self._process_input()
+ self.scale_factor: int = self.args.scale
+ self._rescale()
+
+ self._legend_width = self.width // 2
+ """width of the area used for text annotations/legend"""
+
# temporarily sort by ascending origin attribute and assign the draw indent
self._region_list.sort(key=lambda x: x.origin, reverse=False)
region_indent = 0
@@ -50,40 +58,50 @@ def __init__(self):
# sort in descending order so largest regions are drawn first in z-order (background)
self._region_list.sort(key=lambda x: x.size, reverse=True)
+ self._generate()
+
+ def _generate(self):
# output image diagram
self._create_diagram(self._region_list)
# output markdown report (refs image)
self._create_markdown(self._region_list)
+ def _rescale(self):
+ self.height = self.height * self.scale_factor
+ self.width = self.width * self.scale_factor
+ # self.default_region_text_size = self.default_region_text_size * self.scale_factor
+
def _create_diagram(self, region_list: List[mm.types.MemoryRegion]):
# init the main image
- img_main = PIL.Image.new("RGB", (MemoryMap.width, MemoryMap.height), color=MemoryMap.bgcolour)
+ img_main = PIL.Image.new("RGB", (self.width, self.height), color=self.bgcolour)
- # add a new layer (region_img) for each region block
- # to the main image object (img_main)
+ # paste each new graphic element image to main image
for region in region_list:
- region.create_img(img_width=MemoryMap.width - self._legend_width)
+ # Regions
+ region.create_img(img_width=(self.width - self._legend_width), font_size=self.default_region_text_size)
if not region.img:
continue
img_main.paste(region.img, (self._legend_width + region.draw_indent, region.origin), region.img)
# Origin Address Text
- origin_text_label = mm.types.TextLabel(region._origin, 8)
+ origin_text_label = mm.types.TextLabel(region._origin, self.fixed_legend_text_size)
img_main.paste(origin_text_label.img, (0, region.origin - origin_text_label.height + 1))
# Region End Address Text
end_addr_val = region.origin + region.size
- end_addr_text_label = mm.types.TextLabel(hex(end_addr_val).upper(), 8)
+ end_addr_text_label = mm.types.TextLabel(hex(end_addr_val), self.fixed_legend_text_size)
img_main.paste(end_addr_text_label.img, (0, end_addr_val - end_addr_text_label.height + 1))
+ # Dash Lines from text to region
line_width = 1
line_canvas = PIL.ImageDraw.Draw(img_main)
dash_gap = 4
dash_len = dash_gap / 2
- for x in range(40, 90, dash_gap):
+ for x in range(end_addr_text_label.width * 2, self._legend_width - 10, dash_gap):
line_canvas.line((x, end_addr_val - line_width, x + dash_len, end_addr_val - line_width), fill="black", width=line_width)
+ for x in range(origin_text_label.width * 2, self._legend_width - 10, dash_gap):
line_canvas.line((x, region.origin - line_width, x + dash_len, region.origin - line_width), fill="black", width=1)
# rotate the entire diagram so the origin is at the bottom
@@ -109,10 +127,12 @@ def _process_input(self) -> List[mm.types.MemoryRegion]:
self.parser.add_argument("-o", "--out", help='path to the markdown output report file. Default: "out/report.md"',
default="out/report.md")
self.parser.add_argument("-l", "--limit", help="The maximum memory address for the diagram. Default: 400", default=400, type=int)
+ self.parser.add_argument("-s", "--scale", help="The scale factor for the diagram. Default: 1", default=1, type=int)
+
self.args = self.parser.parse_args()
if self.args.limit:
- MemoryMap.height = self.args.limit
+ self.height = self.args.limit
if len(sys.argv) == 1:
self.parser.error("must pass in data points")
@@ -135,7 +155,7 @@ def _process_input(self) -> List[mm.types.MemoryRegion]:
region_list.append(mm.types.MemoryRegion(name, origin, size))
for r in region_list:
- r.calc_nearest_region(region_list)
+ r.calc_nearest_region(region_list, self.height)
return region_list
@@ -149,4 +169,5 @@ def _batched(self, iterable, n):
if __name__ == "__main__":
- MemoryMap()
+ d = MemoryMap()
+
diff --git a/mm/types.py b/mm/types.py
index 406f38c..452c4d9 100644
--- a/mm/types.py
+++ b/mm/types.py
@@ -77,8 +77,8 @@ def _pick_available_colour(self):
logging.debug(f"\t### {len(MemoryRegion._remaining_colours)} colours left ###")
return chosen_colour_name
- def calc_nearest_region(self, region_list: List['MemoryRegion']):
- import mm.diagram
+ def calc_nearest_region(self, region_list: List['MemoryRegion'], diagram_height: int):
+
"""Calculate the remaining number of bytes until next region block"""
region_distances = {}
logging.debug(f"Calculating nearest distances to {self.name} region:")
@@ -132,15 +132,15 @@ def calc_nearest_region(self, region_list: List['MemoryRegion']):
lowest = min(region_distances, key=region_distances.get)
self.remain = hex(region_distances[lowest])
else:
- self.remain = hex(mm.diagram.MemoryMap.height - this_region_end)
+ self.remain = hex(diagram_height - this_region_end)
elif self.collisons and not self.remain:
- self.remain = hex(mm.diagram.MemoryMap.height - this_region_end)
+ self.remain = hex(diagram_height - this_region_end)
@typeguard.typechecked
class MemoryRegion(Region):
- def create_img(self, img_width: int):
+ def create_img(self, img_width: int, font_size: int):
logging.info(self)
if not self.size:
@@ -160,7 +160,7 @@ def create_img(self, img_width: int):
)
# draw name text
- region_img.paste(TextLabel(text=self.name, font_size=7).img, (5, 5))
+ region_img.paste(TextLabel(text=self.name, font_size=font_size).img, (5, 5))
self.img = region_img
diff --git a/readme.md b/readme.md
index 3331fcb..4c907c2 100644
--- a/readme.md
+++ b/readme.md
@@ -14,7 +14,7 @@ An example can be found in [doc/example/report.md](doc/example/report.md)
### Usage:
```
-usage: mm.diagram [-h] [-o OUT] [-l LIMIT] [regions ...]
+usage: diagram.py [-h] [-o OUT] [-l LIMIT] [-s SCALE] [regions ...]
positional arguments:
regions command line input for regions should be tuples of name, origin and size.
@@ -24,6 +24,8 @@ options:
-o OUT, --out OUT path to the markdown output report file. Default: "out/report.md"
-l LIMIT, --limit LIMIT
The maximum memory address for the diagram. Default: 400
+ -s SCALE, --scale SCALE
+ The scale factor for the diagram. Default: 1
```
- Generate five regions called `kernel`, `rootfs`, `dtb`, `uboot` and `uboot-scr` where four of the five regions intersect/collide. The default report output path is used. Diagram output is shown at the top of the page.
diff --git a/tests/test_args.py b/tests/test_args.py
index d895c9e..0570901 100644
--- a/tests/test_args.py
+++ b/tests/test_args.py
@@ -3,6 +3,7 @@
import mm.diagram
import pathlib
+
# Check the output report at /tmp/pytest/tests.test_args.md
@@ -51,14 +52,20 @@ def test_invalid_out_arg():
def test_valid_default_out_arg():
''' should create default report dir/files '''
+
+ report = pathlib.Path("out/report.md")
+ image = pathlib.Path("out/report.png")
+ report.unlink(missing_ok=True)
+ image.unlink(missing_ok=True)
+
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'a',
'0x10',
'0x10']):
mm.diagram.MemoryMap()
- assert pathlib.Path("out/report.md").exists()
- assert pathlib.Path("out/report.png").exists()
+ assert report.exists()
+ assert image.exists()
def test_invalid_duplicate_name_arg():
@@ -77,13 +84,19 @@ def test_invalid_duplicate_name_arg():
def test_valid_custom_out_arg():
''' should create custom report dir/files '''
+
+ report = pathlib.Path(f"/tmp/pytest/{__name__}.md")
+ image = pathlib.Path(f"/tmp/pytest/{__name__}.png")
+ report.unlink(missing_ok=True)
+ image.unlink(missing_ok=True)
+
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'a',
'0x10',
'0x10',
"-o",
- f"/tmp/pytest/{__name__}.md"]):
+ str(report)]):
mm.diagram.MemoryMap()
- assert pathlib.Path(f"/tmp/pytest/{__name__}.md").exists()
- assert pathlib.Path(f"/tmp/pytest/{__name__}.png").exists()
+ assert report.exists()
+ assert image.exists()
diff --git a/tests/test_distance.py b/tests/test_distance.py
index ce7c27d..b6e0f80 100644
--- a/tests/test_distance.py
+++ b/tests/test_distance.py
@@ -7,6 +7,7 @@
def test_distance_three_regions_same_size_no_collisions():
""" """
+ diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
@@ -21,8 +22,7 @@ def test_distance_three_regions_same_size_no_collisions():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
- "1000"],
- mm.diagram.MemoryMap.height, 1000):
+ str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
@@ -42,6 +42,7 @@ def test_distance_three_regions_same_size_no_collisions():
def test_distance_three_regions_touching_no_collisions():
""" """
+ diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
@@ -56,8 +57,7 @@ def test_distance_three_regions_touching_no_collisions():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
- "1000"],
- mm.diagram.MemoryMap.height, 1000):
+ str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
@@ -74,8 +74,10 @@ def test_distance_three_regions_touching_no_collisions():
assert region._size == "0x30"
assert region.remain == "0x348"
+
def test_distance_three_regions_diff_size_no_collisions():
""" """
+ diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
@@ -90,8 +92,7 @@ def test_distance_three_regions_diff_size_no_collisions():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
- "1000"],
- mm.diagram.MemoryMap.height, 1000):
+ str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
@@ -110,6 +111,7 @@ def test_distance_three_regions_diff_size_no_collisions():
def test_distance_three_regions_bottom_collision():
""" """
+ diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
@@ -124,8 +126,7 @@ def test_distance_three_regions_bottom_collision():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
- "1000"],
- mm.diagram.MemoryMap.height, 1000):
+ str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
@@ -144,6 +145,7 @@ def test_distance_three_regions_bottom_collision():
def test_distance_three_regions_bottom_middle_collision():
""" """
+ diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
@@ -158,8 +160,7 @@ def test_distance_three_regions_bottom_middle_collision():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
- "1000"],
- mm.diagram.MemoryMap.height, 1000):
+ str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
@@ -178,6 +179,7 @@ def test_distance_three_regions_bottom_middle_collision():
def test_distance_five_regions_bottom_top_collision():
""" """
+ diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
@@ -198,8 +200,7 @@ def test_distance_five_regions_bottom_top_collision():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
- "1000"],
- mm.diagram.MemoryMap.height, 1000):
+ str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
diff --git a/tests/test_docs.py b/tests/test_docs.py
index 5b95119..90f3a9c 100644
--- a/tests/test_docs.py
+++ b/tests/test_docs.py
@@ -21,8 +21,8 @@ def test_generate_doc_example():
'uboot-scr',
'0x110',
'0x30',
- "-o",
- "doc/example/report.md"],
+ "-o", "doc/example/report.md",
+ "-s", "2"],
):
d = mm.diagram.MemoryMap()
for region in d._region_list:
diff --git a/tests/test_scaling.py b/tests/test_scaling.py
new file mode 100644
index 0000000..50f43c5
--- /dev/null
+++ b/tests/test_scaling.py
@@ -0,0 +1,70 @@
+import mm.diagram
+import unittest
+import mm.types
+import PIL.Image
+import pathlib
+
+
+def test_scaling_x1():
+ """ """
+ default_diagram_width = 400
+ requested_diagram_height = 400
+ requested_scale = 1
+ report_path = pathlib.Path(f"/tmp/pytest/{__name__}.md")
+ image_path = pathlib.Path(f"/tmp/pytest/{__name__}.png")
+ report_path.unlink(missing_ok=True)
+ image_path.unlink(missing_ok=True)
+
+ with unittest.mock.patch('sys.argv',
+ ['mmap_digram.diagram',
+ 'kernel',
+ '0x10',
+ '0x30',
+ 'rootfs',
+ '0x50',
+ '0x30',
+ 'dtb',
+ '0x90',
+ '0x30',
+ "-o",
+ str(report_path),
+ "-l",
+ str(requested_diagram_height)]):
+
+ d = mm.diagram.MemoryMap()
+ outimg = PIL.Image.open(str(image_path))
+ assert d.height == requested_diagram_height * requested_scale
+ assert d.width == default_diagram_width * requested_scale
+ assert outimg.size == (d.width, d.height)
+
+
+def test_scaling_x2():
+ """ """
+ default_diagram_width = 400
+ requested_diagram_height = 400
+ requested_scale = 2
+ report_path = pathlib.Path(f"/tmp/pytest/{__name__}.md")
+ image_path = pathlib.Path(f"/tmp/pytest/{__name__}.png")
+ report_path.unlink(missing_ok=True)
+ image_path.unlink(missing_ok=True)
+
+ with unittest.mock.patch('sys.argv',
+ ['mmap_digram.diagram',
+ 'kernel',
+ '0x10',
+ '0x30',
+ 'rootfs',
+ '0x50',
+ '0x30',
+ 'dtb',
+ '0x90',
+ '0x30',
+ "-o", str(report_path),
+ "-l", str(requested_diagram_height),
+ "-s", str(requested_scale)]):
+
+ d = mm.diagram.MemoryMap()
+ outimg = PIL.Image.open(str(image_path))
+ assert d.height == requested_diagram_height * requested_scale
+ assert d.width == default_diagram_width * requested_scale
+ assert outimg.size == (d.width, d.height)