OK, I played around with the dithering. (I’m still an engraving newbie.) I used images with resolution 540dpi (an even multiple of the 270 lines per inch) and settings
Glowforge’s dithering was worst. 805LaserSpot’s style (and my imitation) were best, showing detail much better than Halftone. (That might just be that the Halftone grid is a little larger.)
Show my dithering script
import argparse
from PIL import Image
import numpy as np
import os
parser = argparse.ArgumentParser()
parser.add_argument("-in", dest="input_path", required=True, help="Input TIFF file")
parser.add_argument("-out", dest="output_path", required=False, help="Output file path (optional)")
args = parser.parse_args()
input_path = args.input_path
if args.output_path:
output_path = args.output_path
else:
base, ext = os.path.splitext(input_path)
output_path = f"{base}-dithered{ext}"
size = 10
threshold_matrix = np.array([
[0, 10, 66, 128, 180, 231, 198, 157, 110, 115],
[15, 5, 20, 77, 139, 190, 236, 208, 167, 126],
[72, 25, 30, 36, 87, 149, 200, 242, 218, 177],
[133, 82, 41, 46, 51, 97, 159, 211, 247, 229],
[185, 144, 92, 56, 61, 103, 118, 170, 221, 252],
[234, 195, 154, 108, 113, 2, 12, 69, 131, 182],
[193, 239, 206, 164, 123, 18, 7, 23, 79, 141],
[151, 203, 244, 216, 175, 74, 28, 33, 38, 90],
[100, 162, 213, 249, 226, 136, 85, 43, 48, 54],
[105, 121, 172, 224, 255, 188, 146, 95, 59, 64]
], dtype=np.uint8)
img = Image.open(input_path).convert("L")
arr = np.array(img)
# Tile threshold matrix to match image size
h, w = arr.shape
tiled = np.tile(threshold_matrix, (h // size + 1, w // size + 1))[:h, :w]
# Apply ordered dithering
dithered = (arr > tiled).astype(np.uint8) * 255
Image.fromarray(dithered).save(output_path)