From 820eacf9c67cf5353953a5a8541f11aeed8c510c Mon Sep 17 00:00:00 2001 From: Alexander Smirnov <145155732+smalyu@users.noreply.github.com> Date: Mon, 2 Sep 2024 23:59:30 +0500 Subject: [PATCH 1/2] Optimize QRColorMask apply_mask method for enhanced performance This commit introduces optimizations to the apply_mask method in the QRColorMask class to improve performance. Changes include: 1. Replacing getpixel and putpixel with direct pixel manipulation using the load() method, which speeds up the process. 2. Implementing a caching mechanism to reuse color transformations for identical pixel colors, reducing redundant calculations. 3. Adding conditions to skip processing for background color pixels to reduce computational load. These optimizations have significantly reduced the method's execution time. In some experiments, these changes have resulted in performance improvements of over ten times compared to the original method, especially for larger images. --- qrcode/image/styles/colormasks.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/qrcode/image/styles/colormasks.py b/qrcode/image/styles/colormasks.py index 3b9a8084..cb3815ef 100644 --- a/qrcode/image/styles/colormasks.py +++ b/qrcode/image/styles/colormasks.py @@ -32,22 +32,27 @@ def initialize(self, styledPilImage, image): def apply_mask(self, image): width, height = image.size + pixels = image.load() + fg_color_cache = {} for x in range(width): for y in range(height): - norm = self.extrap_color( - self.back_color, self.paint_color, image.getpixel((x, y)) - ) + current_color = pixels[x, y] + if current_color == self.back_color: + continue + if current_color in fg_color_cache: + pixels[x, y] = fg_color_cache[current_color] + continue + norm = self.extrap_color(self.back_color, self.paint_color, current_color) if norm is not None: - image.putpixel( - (x, y), - self.interp_color( - self.get_bg_pixel(image, x, y), - self.get_fg_pixel(image, x, y), - norm, - ), + new_color = self.interp_color( + self.get_bg_pixel(image, x, y), + self.get_fg_pixel(image, x, y), + norm ) + pixels[x, y] = new_color + fg_color_cache[current_color] = new_color else: - image.putpixel((x, y), self.get_bg_pixel(image, x, y)) + pixels[x, y] = self.get_bg_pixel(image, x, y) def get_fg_pixel(self, image, x, y): raise NotImplementedError("QRModuleDrawer.paint_fg_pixel") From 8c8632af7abee4ea46d62b742d8531febb982422 Mon Sep 17 00:00:00 2001 From: Alexander Smirnov <145155732+smalyu@users.noreply.github.com> Date: Fri, 4 Oct 2024 18:00:26 +0300 Subject: [PATCH 2/2] Fix cache-related bug in apply_mask for masks with position-dependent colors - Resolved cache issues in `apply_mask` by setting `use_cache` to `False` by default, preventing errors in masks where pixel position affects color (e.g., RadialGradientColorMask). - Enabled `use_cache` for `SolidFillColorMask`, as pixel position is not relevant, preserving performance where applicable. --- qrcode/image/styles/colormasks.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/qrcode/image/styles/colormasks.py b/qrcode/image/styles/colormasks.py index 495a9c71..668be4fa 100644 --- a/qrcode/image/styles/colormasks.py +++ b/qrcode/image/styles/colormasks.py @@ -27,16 +27,16 @@ class QRColorMask: def initialize(self, styledPilImage, image): self.paint_color = styledPilImage.paint_color - def apply_mask(self, image): + def apply_mask(self, image, use_cache=False): width, height = image.size pixels = image.load() - fg_color_cache = {} + fg_color_cache = {} if use_cache else None for x in range(width): for y in range(height): current_color = pixels[x, y] if current_color == self.back_color: continue - if current_color in fg_color_cache: + if use_cache and current_color in fg_color_cache: pixels[x, y] = fg_color_cache[current_color] continue norm = self.extrap_color(self.back_color, self.paint_color, current_color) @@ -47,7 +47,9 @@ def apply_mask(self, image): norm ) pixels[x, y] = new_color - fg_color_cache[current_color] = new_color + + if use_cache: + fg_color_cache[current_color] = new_color else: pixels[x, y] = self.get_bg_pixel(image, x, y) @@ -108,7 +110,7 @@ def apply_mask(self, image): # the individual pixel comparisons that the base class uses, which # would be a lot faster. (In fact doing this would probably remove # the need for the B&W optimization above.) - QRColorMask.apply_mask(self, image) + QRColorMask.apply_mask(self, image, use_cache=True) def get_fg_pixel(self, image, x, y): return self.front_color