9 Commits
6 changed files with 5671 additions and 52 deletions
+35 -2
View File
@@ -2,9 +2,11 @@
A set of ComfyUI nodes to dynamically create QR image layers for generative QR art without the hassle of leaving the webui.
Everything in this pack is for the purpose of creating patterns. If you would like to evaluate rendered patterns for scannability, check out the companion project [Comfy QR Validation Nodes](https://gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes). Any workflow in the example that ends with "validated" assume the installation of the validation pack as well.
This repository is managed publicly on [Gitlab](https://gitlab.com/sofuego-comfy-nodes/comfy-qr), but also mirrored on [Github](https://github.com/coreyryanhanson/comfy-qr). Please submit any [issues](https://gitlab.com/sofuego-comfy-nodes/comfy-qr/-/issues) or [pull requests](https://gitlab.com/sofuego-comfy-nodes/comfy-qr/-/merge_requests) to the gitlab repo.
![QR code with repo link](img/badgers.png)
![QR code with repo link](img/badgers_levels_adjusted.png)
## Getting started
@@ -71,6 +73,37 @@ A QR code fixed to specific output dimensions through image resampling.
* `IMAGE` - The QR code
* `QR_VERSION` - An integer of the QR version that was selected based on the text length and error correction level.
### QR Code (Split)
A version of the `QR Code` node that also outputs split layers for the function patterns and a mask for the function patterns.
#### Inputs
* `protocol` - If enabled this will prefix the textbox input with a preset to represent the internet protocol. This is included both for convenience and as a workaround for the textbox clipping strings with this character combination.
* `Http` - Adds "http://" before the text.
* `Https` - Adds "https://" before the text.
* `None` - Uses only the contents of the `text` box.
* `text` - What text to build your QR code with. If `protocol` is specified, this textbox will be combined it with the selected option.
* `module_size` - The pixel width of the smallest unit of a QR code.
* `max_image_size` - The maximum size of the resulting QR code. If the combination of `text`, `module_size`, and `error_correction` create dimensions that exceed this, an error will halt the pipeline.
* `fill_hexcolor` - A string of characters representing the hex value of the QR units. Can be 3 or 6 hexadecimal characters with an optional # before.
* `back_hexcolor` - A string of characters representing the hex value of the space around the QR units. Can be 3 or 6 hexadecimal characters with an optional # before.
* `error_correction` - The level of error correction to apply.
* `Low` - 7% error correction.
* `Medium` - 15% error correction.
* `Quartile` - 25% error correction.
* `High` - 30% error correction.
* `border` - The border size (In multiples of `module` widths)
#### Outputs
* `QR_FLATTENED` - The QR code
* `MODULE_LAYER` - The QR code with the `back_hexcolor` filling in the function patterns (to remove their influence from the early diffusion steps).
* `FINDER_LAYER` - The isolated finder patterns over the `back_hexcolor`.
* `FINDER_MASK` - A mask that isolates the locations of the 3 finder patterns.
* `QR_VERSION` - An integer of the QR version that was selected based on the text length and error correction level.
* `IMAGE_SIZE` - An integer based on the pixel width of the resulting QR code.
## Future updates
If the solution is clean enough, I plan on implementing a node that will automatically generate a mask to extract the 3 QR markers (based on `module_size`, `border`, and `QR_VERSION`) for more advanced workflows.
If the solution is clean enough and if it can definitively improve scanability, there may be additional plans for the seperation of alignment patterns (based on `module_size`, `border`, and `QR_VERSION`) for more advanced workflows.
+47 -47
View File
@@ -127,10 +127,10 @@
678,
604
],
"size": [
315,
106
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 5,
"mode": 0,
@@ -312,7 +312,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
679629273700059,
483918444996335,
"randomize",
20,
8,
@@ -321,6 +321,45 @@
1
]
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
656,
351
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
23
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"worst quality"
]
},
{
"id": 14,
"type": "comfy-qr-by-module-size",
@@ -371,55 +410,16 @@
16,
512,
"#000000",
"#999999",
"#FFFFFF",
"Medium",
1
]
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
656,
351
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
23
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"worst quality"
]
},
{
"id": 17,
"type": "ControlNetApplyAdvanced",
"pos": [
808,
804,
796
],
"size": {
@@ -475,7 +475,7 @@
"Node name for S&R": "ControlNetApplyAdvanced"
},
"widgets_values": [
2.49999999999999,
1.6,
0,
1
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

+86 -3
View File
@@ -7,6 +7,8 @@ import torch
class QRBase:
def __init__(self):
self.text = ""
self.fill = None
self.back = None
FUNCTION = "generate_qr"
CATEGORY = "Comfy-QR"
@@ -25,10 +27,10 @@ class QRBase:
return torch.from_numpy(out_image).unsqueeze(0)
def _make_qr(self, qr, fill_hexcolor, back_hexcolor):
fill_color = self._parse_hexcolor_string(fill_hexcolor, "fill_hexcolor")
back_color = self._parse_hexcolor_string(back_hexcolor, "back_hexcolor")
self.fill = self._parse_hexcolor_string(fill_hexcolor, "fill_hexcolor")
self.back = self._parse_hexcolor_string(back_hexcolor, "back_hexcolor")
qr.make(fit=True)
return qr.make_image(fill_color=fill_color, back_color=back_color)
return qr.make_image(fill_color=self.fill, back_color=self.back)
def _parse_hexcolor_string(self, s, parameter):
if s.startswith("#"):
@@ -171,13 +173,94 @@ class QRByModuleSize(QRBase):
return (self._img_to_tensor(img), qr.version, img.pixel_size)
class QRByModuleSizeSplitFunctionPatterns(QRBase):
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"protocol": (["Http", "Https", "None"], {"default": "Https"}),
"text": ("STRING", {"multiline": True}),
"module_size": ("INT", {"default": 16, "min": 1, "max": 64, "step": 1}),
"max_image_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}),
"fill_hexcolor": ("STRING", {"multiline": False, "default": "#000000"}),
"back_hexcolor": ("STRING", {"multiline": False, "default": "#FFFFFF"}),
"error_correction": (["Low", "Medium", "Quartile", "High"], {"default": "High"}),
"border": ("INT", {"default": 1, "min": 0, "max": 100, "step": 1}),
},
}
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK", "INT", "INT")
RETURN_NAMES = ("QR_FLATTENED", "MODULE_LAYER", "FINDER_LAYER", "FINDER_MASK", "QR_VERSION", "IMAGE_SIZE")
def _generate_finder_pattern_ranges(self, module_size, border_size):
outer = module_size * border_size
inner = 7 * module_size + outer
return [
(outer, inner, outer, inner),
(outer, inner, -inner, -outer),
(-inner, -outer, outer, inner)
]
def _generate_finder_pattern_mask(self, pixel_size, module_size, border_size):
mask = np.zeros((pixel_size, pixel_size), dtype=bool)
for x_min, x_max, y_min, y_max in self._generate_finder_pattern_ranges(module_size, border_size):
mask[y_min:y_max, x_min:x_max] = True
return mask
def _apply_fill_to_mask(self, img, mask):
array = np.array(img).copy()
indices = np.nonzero(mask)
array[indices[0], indices[1], :] = self.back
return Image.fromarray(array)
def _mask_to_tensor(self, mask):
out_image = mask.astype(np.float32)
return torch.from_numpy(out_image)
def generate_qr(
self,
protocol,
text,
module_size,
max_image_size,
fill_hexcolor,
back_hexcolor,
error_correction,
border
):
self.update_text(protocol, text)
error_level = self._get_error_correction_constant(error_correction)
qr = qrcode.QRCode(
error_correction=error_level,
box_size=module_size,
border=border)
qr.add_data(self.text)
img = self._make_qr(qr, fill_hexcolor, back_hexcolor)
pixel_size = img.pixel_size
self._validate_qr_size(pixel_size, max_image_size)
mask = self._generate_finder_pattern_mask(pixel_size, module_size, border)
module_image = self._apply_fill_to_mask(img, mask)
function_image = self._apply_fill_to_mask(img, ~mask)
return (
self._img_to_tensor(img),
self._img_to_tensor(module_image),
self._img_to_tensor(function_image),
self._mask_to_tensor(mask),
qr.version,
pixel_size,
)
NODE_CLASS_MAPPINGS = {
"comfy-qr-by-module-size": QRByModuleSize,
"comfy-qr-by-image-size": QRByImageSize,
"comfy-qr-by-module-split": QRByModuleSizeSplitFunctionPatterns,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"comfy-qr-by-module-size": "QR Code",
"comfy-qr-by-image-size": "QR Code (Conformed to Image Size)",
"comfy-qr-by-module-split": "QR Code (Split)",
}