Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8023feac8 | ||
|
|
3d0c12bdff | ||
|
|
fd35bb8e62 | ||
|
|
c92488f903 | ||
|
|
c614f70082 |
@@ -2,8 +2,12 @@
|
||||
|
||||
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 creation of 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.
|
||||
|
||||

|
||||
|
||||
## Getting started
|
||||
|
||||
This project currently contains two nodes for generating QR codes.
|
||||
@@ -69,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 finder patterns along with an accompanying mask.
|
||||
|
||||
#### 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 scannability, there may be additional plans for the seperation of alignment patterns (based on `module_size`, `border`, and `QR_VERSION`) for more advanced workflows.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .qr_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -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 |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
+88
-3
@@ -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,96 @@ 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
|
||||
# Alternate behavior is required to prevent bugs from 0 border_size.
|
||||
far_outer = -outer if border_size else None
|
||||
return [
|
||||
(outer, inner, outer, inner),
|
||||
(outer, inner, -inner, far_outer),
|
||||
(-inner, far_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)",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user