File size: 12,330 Bytes
2a39f76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
import streamlit as st
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import (
    RoundedModuleDrawer, CircleModuleDrawer,
    GappedSquareModuleDrawer, SquareModuleDrawer
)
from qrcode.image.styles.colormasks import SolidFillColorMask
from PIL import Image
import io
import re

# Helper function to convert hex color to RGB tuple
def hex_to_rgb(hex_color):
    """Convert hex color string to RGB tuple"""
    hex_color = hex_color.lstrip('#')
    return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

# Page configuration
st.set_page_config(
    page_title="QR Code Generator",
    page_icon="πŸ“±",
    layout="centered",
    initial_sidebar_state="collapsed"
)

# Custom CSS for better UX
st.markdown("""
<style>
    .big-input textarea {
        font-size: 18px !important;
    }
    .template-button {
        text-align: center;
        padding: 10px;
        border-radius: 8px;
        cursor: pointer;
    }
    .stDownloadButton button {
        width: 100%;
        background-color: #4CAF50;
        color: white;
        font-size: 18px;
        padding: 12px;
        border-radius: 8px;
    }
</style>
""", unsafe_allow_html=True)

# Initialize session state
if 'qr_data' not in st.session_state:
    st.session_state.qr_data = "https://streamlit.io"
if 'template_type' not in st.session_state:
    st.session_state.template_type = "url"
if 'show_advanced' not in st.session_state:
    st.session_state.show_advanced = False

# Header
st.title("πŸ“± QR Code Generator")
st.markdown("**Free & Open Source** β€’ No Ads β€’ Privacy-Friendly")

# ============ TEMPLATES (QUICK START) ============
st.markdown("### πŸš€ Quick Start Templates")

templates = [
    ("URL", "🌐", "url", "https://example.com"),
    ("WiFi", "πŸ“Ά", "wifi", ""),
    ("Email", "πŸ“§", "email", "mailto:name@example.com"),
    ("Phone", "πŸ“ž", "phone", "tel:+1234567890"),
    ("Text", "πŸ’¬", "text", "Hello World!")
]

# Create responsive grid layout (3 columns on first row, 2 on second)
col1, col2, col3 = st.columns(3)
cols = [col1, col2, col3]

for idx, (name, icon, template_id, placeholder) in enumerate(templates[:3]):
    with cols[idx]:
        if st.button(f"{icon}\n{name}", use_container_width=True, key=f"btn_{template_id}"):
            st.session_state.template_type = template_id
            if template_id == "wifi":
                st.session_state.qr_data = ""
            else:
                st.session_state.qr_data = placeholder
            st.rerun()

col4, col5, col6 = st.columns(3)
cols2 = [col4, col5, col6]

for idx, (name, icon, template_id, placeholder) in enumerate(templates[3:]):
    with cols2[idx]:
        if st.button(f"{icon}\n{name}", use_container_width=True, key=f"btn_{template_id}"):
            st.session_state.template_type = template_id
            if template_id == "wifi":
                st.session_state.qr_data = ""
            else:
                st.session_state.qr_data = placeholder
            st.rerun()

# ============ SMART INPUT AREA ============
st.markdown("---")

# Template-specific input
if st.session_state.template_type == "wifi":
    st.markdown("### πŸ“Ά WiFi Configuration")
    col1, col2 = st.columns(2)
    with col1:
        wifi_ssid = st.text_input("Network Name (SSID)", placeholder="MyWiFi")
        wifi_password = st.text_input("Password", type="password", placeholder="password123")
    with col2:
        wifi_security = st.selectbox("Security Type", ["WPA", "WEP", "nopass"], index=0)
        wifi_hidden = st.checkbox("Hidden Network", value=False)

    # Generate WiFi QR format
    if wifi_ssid:
        hidden_str = "true" if wifi_hidden else "false"
        if wifi_security == "nopass":
            qr_data = f"WIFI:T:{wifi_security};S:{wifi_ssid};H:{hidden_str};;"
        else:
            qr_data = f"WIFI:T:{wifi_security};S:{wifi_ssid};P:{wifi_password};H:{hidden_str};;"
    else:
        qr_data = ""
else:
    # Standard input for all other types
    input_labels = {
        "url": "🌐 Enter your URL or website",
        "email": "πŸ“§ Enter email address or mailto link",
        "phone": "πŸ“ž Enter phone number",
        "text": "πŸ’¬ Enter any text"
    }

    label = input_labels.get(st.session_state.template_type, "Enter your content")

    qr_data = st.text_area(
        label,
        value=st.session_state.qr_data,
        height=100,
        placeholder="Type or paste your content here...",
        key=f"input_{st.session_state.template_type}"
    )
    st.session_state.qr_data = qr_data

# Input validation and helpful feedback
validation_message = ""
validation_type = "info"

if qr_data:
    # URL validation
    if st.session_state.template_type == "url":
        url_pattern = re.compile(r'^https?://')
        if not url_pattern.match(qr_data):
            validation_message = "πŸ’‘ Tip: URLs should start with http:// or https://"
            validation_type = "warning"

    # Character count
    char_count = len(qr_data)
    if char_count > 2000:
        validation_message = f"⚠️ Warning: {char_count} characters may result in a complex QR code"
        validation_type = "warning"
    elif char_count > 500:
        validation_message = f"ℹ️ {char_count} characters - consider using a URL shortener for easier scanning"
        validation_type = "info"

if validation_message:
    if validation_type == "warning":
        st.warning(validation_message)
    else:
        st.info(validation_message)

# ============ CUSTOMIZATION (COLLAPSED BY DEFAULT) ============
with st.expander("🎨 Customize Appearance (Optional)", expanded=False):

    tab1, tab2, tab3 = st.tabs(["Colors & Style", "Size", "Logo"])

    with tab1:
        col1, col2 = st.columns(2)
        with col1:
            fill_color = st.color_picker("QR Code Color", "#000000")
            module_style = st.selectbox(
                "Shape Style",
                options=["Square", "Rounded", "Circle", "Gapped Square"],
                help="Shape of the QR code modules"
            )
        with col2:
            back_color = st.color_picker("Background Color", "#FFFFFF")
            error_correction = st.select_slider(
                "Error Correction",
                options=["Low", "Medium", "High", "Very High"],
                value="Medium",
                help="Higher levels make QR code more readable if damaged"
            )

    with tab2:
        box_size = st.slider(
            "Resolution (pixels per module)",
            min_value=5,
            max_value=30,
            value=10,
            help="Higher = larger file size but better quality"
        )
        border_size = st.slider(
            "Border Thickness",
            min_value=1,
            max_value=10,
            value=4
        )

    with tab3:
        st.markdown("Add a logo or image to the center of your QR code")
        logo_file = st.file_uploader(
            "Upload logo",
            type=["png", "jpg", "jpeg"],
            help="Best results with square, transparent PNG images"
        )

        if logo_file:
            logo_size_percent = st.slider(
                "Logo size (%)",
                min_value=10,
                max_value=40,
                value=20,
                help="Logo shouldn't exceed 30% for reliable scanning"
            )
        else:
            logo_size_percent = 20
            st.info("πŸ’‘ Tip: Use high error correction with logos to maintain scannability")

# ============ QR CODE GENERATION ============

# Maps
error_correction_map = {
    "Low": qrcode.constants.ERROR_CORRECT_L,
    "Medium": qrcode.constants.ERROR_CORRECT_M,
    "High": qrcode.constants.ERROR_CORRECT_Q,
    "Very High": qrcode.constants.ERROR_CORRECT_H
}

module_drawer_map = {
    "Square": SquareModuleDrawer(),
    "Rounded": RoundedModuleDrawer(),
    "Circle": CircleModuleDrawer(),
    "Gapped Square": GappedSquareModuleDrawer()
}

def generate_qr_code(data, fill, back, style, error_lvl, box, border, logo, logo_size):
    """Generate QR code with given parameters"""
    try:
        # Convert hex colors to RGB tuples
        fill_rgb = hex_to_rgb(fill)
        back_rgb = hex_to_rgb(back)

        qr = qrcode.QRCode(
            version=1,
            error_correction=error_correction_map[error_lvl],
            box_size=box,
            border=border,
        )

        qr.add_data(data)
        qr.make(fit=True)

        img = qr.make_image(
            image_factory=StyledPilImage,
            module_drawer=module_drawer_map[style],
            color_mask=SolidFillColorMask(
                back_color=back_rgb,
                front_color=fill_rgb
            )
        )

        # Convert to standard PIL Image for Streamlit compatibility
        img = img.convert('RGB')

        # Add logo if provided
        if logo is not None:
            logo_img = Image.open(logo)

            # Calculate logo size
            qr_width, qr_height = img.size
            logo_max_size = min(qr_width, qr_height) * logo_size // 100

            # Resize logo maintaining aspect ratio
            logo_img.thumbnail((logo_max_size, logo_max_size), Image.Resampling.LANCZOS)

            # Create a white background for logo if it doesn't have transparency
            if logo_img.mode != 'RGBA':
                logo_bg = Image.new('RGB', logo_img.size, 'white')
                logo_bg.paste(logo_img, (0, 0))
                logo_img = logo_bg

            # Calculate position to center logo
            logo_pos = (
                (qr_width - logo_img.width) // 2,
                (qr_height - logo_img.height) // 2
            )

            # Paste logo
            img.paste(logo_img, logo_pos, logo_img if logo_img.mode == 'RGBA' else None)

        return img, qr
    except Exception as e:
        st.error(f"Error generating QR code: {str(e)}")
        return None, None

# ============ DISPLAY QR CODE ============
st.markdown("---")

if qr_data:
    img, qr = generate_qr_code(
        qr_data, fill_color, back_color, module_style,
        error_correction, box_size, border_size, logo_file, logo_size_percent
    )

    if img:
        # Display QR code
        col1, col2, col3 = st.columns([1, 3, 1])
        with col2:
            st.image(img, use_container_width=True)

        # Download button (prominent)
        buf = io.BytesIO()
        img.save(buf, format="PNG")
        byte_im = buf.getvalue()

        st.download_button(
            label="⬇️ Download QR Code",
            data=byte_im,
            file_name="qr_code.png",
            mime="image/png",
            use_container_width=True
        )

        # QR Code info
        col1, col2, col3 = st.columns(3)
        with col1:
            st.metric("Size", f"{img.size[0]}Γ—{img.size[1]}px")
        with col2:
            st.metric("Version", qr.version)
        with col3:
            st.metric("Characters", len(qr_data))

else:
    # Show helpful message when empty
    st.info("πŸ‘† Enter content above or select a template to generate your QR code")

    # Show example QR code
    example_img, _ = generate_qr_code(
        "https://streamlit.io",
        "#000000", "#FFFFFF", "Square", "Medium", 10, 4, None, 20
    )
    if example_img:
        col1, col2, col3 = st.columns([1, 2, 1])
        with col2:
            st.image(example_img, caption="Example QR Code", use_container_width=True)

# ============ HELPFUL TIPS ============
with st.expander("ℹ️ Tips & Examples"):
    st.markdown("""
    ### πŸ“ Format Examples:

    **Website/URL:**
    - `https://www.example.com`
    - `https://mysite.com/page?id=123`

    **Email:**
    - `mailto:name@example.com`
    - `mailto:name@example.com?subject=Hello&body=Message`

    **Phone:**
    - `tel:+1234567890`

    **SMS:**
    - `sms:+1234567890`
    - `sms:+1234567890?body=Hello there`

    **WiFi** (use WiFi template):
    - Automatically formatted when you fill the form

    **Plain Text:**
    - Any text you want to encode

    ### πŸ’‘ Best Practices:
    - Keep content short for easier scanning
    - Use URL shorteners for long links
    - Test QR codes before printing
    - Use high error correction if adding logos
    - Ensure good contrast (dark on light background)
    - Maintain minimum size (2Γ—2 cm for print)
    """)