perf: vectorize shift_image with NumPy, fixes #520
Replace the per-pixel getpixel/putpixel loop in shift_image (py/imagefunc.py:328-349) with NumPy slicing: np.roll for the cyclic branch, boolean row/col masks + np.ix_ for the non-cyclic branch. Function signature is unchanged; none of the 14 call sites (ImageShift, DropShadow v1-v3, InnerShadow v1-v2, ChannelShake) need edits. Benchmark on Apple M4, CPU only, mean of N runs: - 1024x1024 non-cyclic: 0.563s -> 0.030s (18.5x) - 2048x2048 non-cyclic: 2.448s -> 0.117s (21.0x) - 1024x1024 cyclic: 0.608s -> 0.004s (159x) RGB output verified pixel-identical against the original implementation across 2592 parameter combinations plus six full-1024x1024 cases. Fixes #520
This commit is contained in:
+12
-19
@@ -328,25 +328,18 @@ def blend_hard_mix(background_image:Image, layer_image:Image) -> Image:
|
||||
def shift_image(image:Image, distance_x:int, distance_y:int, background_color:str='#000000', cyclic:bool=False) -> Image:
|
||||
width = image.width
|
||||
height = image.height
|
||||
ret_image = Image.new('RGB', size=(width, height), color=background_color)
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
if cyclic:
|
||||
orig_x = x + distance_x
|
||||
if orig_x > width-1 or orig_x < 0:
|
||||
orig_x = abs(orig_x % width)
|
||||
orig_y = y + distance_y
|
||||
if orig_y > height-1 or orig_y < 0:
|
||||
orig_y = abs(orig_y % height)
|
||||
|
||||
pixel = image.getpixel((orig_x, orig_y))
|
||||
ret_image.putpixel((x, y), pixel)
|
||||
else:
|
||||
if x > -distance_x and y > -distance_y: # 防止回转
|
||||
if x + distance_x < width and y + distance_y < height: # 防止越界
|
||||
pixel = image.getpixel((x + distance_x, y + distance_y))
|
||||
ret_image.putpixel((x, y), pixel)
|
||||
return ret_image
|
||||
ret_arr = np.asarray(Image.new('RGB', size=(width, height), color=background_color)).copy()
|
||||
arr = np.asarray(image.convert('RGB'))
|
||||
if cyclic:
|
||||
ret_arr = np.roll(arr, shift=(-distance_y, -distance_x), axis=(0, 1))
|
||||
else:
|
||||
rows = np.arange(height) + distance_y
|
||||
cols = np.arange(width) + distance_x
|
||||
valid_rows = (np.arange(height) > -distance_y) & (rows >= 0) & (rows < height)
|
||||
valid_cols = (np.arange(width) > -distance_x) & (cols >= 0) & (cols < width)
|
||||
if valid_rows.any() and valid_cols.any():
|
||||
ret_arr[np.ix_(valid_rows, valid_cols)] = arr[np.ix_(rows[valid_rows], cols[valid_cols])]
|
||||
return Image.fromarray(ret_arr)
|
||||
|
||||
def chop_image(background_image:Image, layer_image:Image, blend_mode:str, opacity:int) -> Image:
|
||||
ret_image = background_image
|
||||
|
||||
Reference in New Issue
Block a user