{"id":607,"date":"2026-08-12T22:32:55","date_gmt":"2026-08-12T15:32:55","guid":{"rendered":"https:\/\/app.mitunet.com\/home\/?page_id=607"},"modified":"2026-08-15T18:17:51","modified_gmt":"2026-08-15T11:17:51","slug":"lam-mo-anh","status":"publish","type":"page","link":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/","title":{"rendered":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI"},"content":{"rendered":"<div id=\"tdi_1\" class=\"tdc-zone\"><div class=\"tdc_zone tdi_2  wpb_row td-pb-row\"  >\n<style scoped>\n\/* custom css - generated by TagDiv Composer *\/\n\n\/* custom css - generated by TagDiv Composer *\/\n.tdi_2{\r\n                    min-height: 0;\r\n                }\n<\/style><div id=\"tdi_3\" class=\"tdc-row stretch_row_1400 td-stretch-content\"><div class=\"vc_row tdi_4  wpb_row td-pb-row\" >\n<style scoped>\n\/* custom css - generated by TagDiv Composer *\/\n\n\/* custom css - generated by TagDiv Composer *\/\n.tdi_4,\r\n                .tdi_4 .tdc-columns{\r\n                    min-height: 0;\r\n                }.tdi_4,\r\n\t\t\t\t.tdi_4 .tdc-columns{\r\n\t\t\t\t    display: block;\r\n\t\t\t\t}.tdi_4 .tdc-columns{\r\n\t\t\t\t    width: 100%;\r\n\t\t\t\t}.tdi_4:before,\r\n\t\t\t\t.tdi_4:after{\r\n\t\t\t\t    display: table;\r\n\t\t\t\t}\n<\/style><div class=\"vc_column tdi_6  wpb_column vc_column_container tdc-column td-pb-span12\">\n<style scoped>\n\/* custom css - generated by TagDiv Composer *\/\n\n\/* custom css - generated by TagDiv Composer *\/\n.tdi_6{\r\n                    vertical-align: baseline;\r\n                }.tdi_6 > .wpb_wrapper,\r\n\t\t\t\t.tdi_6 > .wpb_wrapper > .tdc-elements{\r\n\t\t\t\t    display: block;\r\n\t\t\t\t}.tdi_6 > .wpb_wrapper > .tdc-elements{\r\n\t\t\t\t    width: 100%;\r\n\t\t\t\t}.tdi_6 > .wpb_wrapper > .vc_row_inner{\r\n\t\t\t\t    width: auto;\r\n\t\t\t\t}.tdi_6 > .wpb_wrapper{\r\n\t\t\t\t    width: auto;\r\n\t\t\t\t    height: auto;\r\n\t\t\t\t}\n<\/style><div class=\"wpb_wrapper\" ><div class=\"vc_row_inner tdi_8  vc_row vc_inner wpb_row td-pb-row\" >\n<style scoped>\n\/* custom css - generated by TagDiv Composer *\/\n\n\/* custom css - generated by TagDiv Composer *\/\n.tdi_8{\r\n                    position: relative !important;\r\n                    top: 0;\r\n                    transform: none;\r\n                    -webkit-transform: none;\r\n                }.tdi_8,\r\n\t\t\t\t.tdi_8 .tdc-inner-columns{\r\n\t\t\t\t    display: block;\r\n\t\t\t\t}.tdi_8 .tdc-inner-columns{\r\n\t\t\t\t    width: 100%;\r\n\t\t\t\t}\n<\/style><div class=\"vc_column_inner tdi_10  wpb_column vc_column_container tdc-inner-column td-pb-span12\">\n<style scoped>\n\/* custom css - generated by TagDiv Composer *\/\n\n\/* custom css - generated by TagDiv Composer *\/\n.tdi_10{\r\n                    vertical-align: baseline;\r\n                }.tdi_10 .vc_column-inner > .wpb_wrapper,\r\n\t\t\t\t.tdi_10 .vc_column-inner > .wpb_wrapper .tdc-elements{\r\n\t\t\t\t    display: block;\r\n\t\t\t\t}.tdi_10 .vc_column-inner > .wpb_wrapper .tdc-elements{\r\n\t\t\t\t    width: 100%;\r\n\t\t\t\t}\n<\/style><div class=\"vc_column-inner\"><div class=\"wpb_wrapper\" ><div class=\"wpb_wrapper wpb_text_column td_block_wrap td_block_wrap vc_column_text tdi_11  tagdiv-type td-pb-border-top td_block_template_1\"  data-td-block-uid=\"tdi_11\" >\n<style>\n\n\/* inline tdc_css att - generated by TagDiv Composer *\/\n\n.tdi_11{\npadding-top:20px !important;\n--tdb-scroll-anim:none !important;\n}\n\n<\/style>\n<style>\n\/* custom css - generated by TagDiv Composer *\/\n.vc_column_text > .td-element-style{\r\n\t\t\t\t\tz-index: -1;\r\n\t\t\t\t}\n<\/style><div class=\"td-fix-index\"><h1><em>C\u00f4ng C\u1ee5 L\u00e0m M\u1edd \u1ea2nh Online Mi\u1ec5n Ph\u00ed, \u1ee8ng D\u1ee5ng L\u00e0m M\u1edd H\u00ecnh \u1ea2nh B\u1eb1ng AI<\/em><\/h1>\n<\/div><\/div><div class=\"wpb_wrapper wpb_text_column td_block_wrap td_block_wrap vc_column_text tdi_12  tagdiv-type td-pb-border-top td_block_template_1\"  data-td-block-uid=\"tdi_12\" ><div class=\"td-fix-index\">\n<div id=\"boa32_348565\" class=\"boa32\">\n\n    <!-- ======================================================\n         UPLOAD\n    ======================================================= -->\n\n    <div class=\"boa32-upload\" data-upload>\n\n        <div class=\"boa32-upload-icon\">\n            \ud83d\uddbc\ufe0f\n        <\/div>\n\n        <h2>\n            L\u00e0m m\u1edd \u1ea3nh online\n        <\/h2>\n\n        <p>\n            L\u00e0m m\u1edd khu\u00f4n m\u1eb7t, v\u1eadt th\u1ec3 ho\u1eb7c v\u00f9ng b\u1ea5t k\u1ef3 tr\u00ean \u1ea3nh\n        <\/p>\n\n        <button\n            type=\"button\"\n            class=\"boa32-primary\"\n            data-select\n        >\n            Ch\u1ecdn \u1ea3nh\n        <\/button>\n\n        <small>\n            JPG \u00b7 PNG \u00b7 WebP \u00b7 t\u1ed1i \u0111a 20MB\n        <\/small>\n\n        <input\n            type=\"file\"\n            hidden\n            data-file\n            accept=\"image\/jpeg,image\/png,image\/webp,image\/heic,image\/heif\"\n        >\n\n    <\/div>\n\n\n    <!-- ======================================================\n         EDITOR\n    ======================================================= -->\n\n    <div\n        class=\"boa32-editor\"\n        data-editor\n        hidden\n    >\n\n        <!-- ==================================================\n             IMAGE AREA\n        =================================================== -->\n\n        <div class=\"boa32-image-column\">\n\n            <div class=\"boa32-image-sticky\">\n\n                <div class=\"boa32-image-header\">\n\n                    <div>\n\n                        <strong>\n                            \u1ea2nh \u0111ang ch\u1ec9nh s\u1eeda\n                        <\/strong>\n\n                        <span data-size><\/span>\n\n                    <\/div>\n\n                    <button\n                        type=\"button\"\n                        class=\"boa32-new\"\n                        data-new\n                        title=\"Ch\u1ecdn \u1ea3nh kh\u00e1c\"\n                    >\n                        \uff0b\n                    <\/button>\n\n                <\/div>\n\n\n                <div class=\"boa32-canvas-wrap\">\n\n                    <canvas data-canvas><\/canvas>\n\n                <\/div>\n\n\n                <div class=\"boa32-image-footer\">\n\n                    <div class=\"boa32-zoom\">\n\n                        <button type=\"button\" data-zoom-out>\n                            \u2212\n                        <\/button>\n\n                        <button type=\"button\" data-zoom-reset>\n                            100%\n                        <\/button>\n\n                        <button type=\"button\" data-zoom-in>\n                            \uff0b\n                        <\/button>\n\n                    <\/div>\n\n                    <span data-zoom-label>\n                        100%\n                    <\/span>\n\n                <\/div>\n\n            <\/div>\n\n        <\/div>\n\n\n        <!-- ==================================================\n             TOOLS\n        =================================================== -->\n\n        <div class=\"boa32-tools-column\">\n\n            <div class=\"boa32-tools-inner\">\n\n                <div class=\"boa32-heading\">\n\n                    <h2>\n                        C\u00f4ng c\u1ee5\n                    <\/h2>\n\n                    <p>\n                        Ch\u1ecdn c\u00f4ng c\u1ee5 r\u1ed3i thao t\u00e1c tr\u1ef1c ti\u1ebfp tr\u00ean \u1ea3nh.\n                    <\/p>\n\n                    <span>\n                        \ud83d\udd12 \u1ea2nh \u0111\u01b0\u1ee3c x\u1eed l\u00fd ngay tr\u00ean tr\u00ecnh duy\u1ec7t\n                    <\/span>\n\n                <\/div>\n\n\n                <!-- ==================================================\n                     NORMAL TOOLS\n                =================================================== -->\n\n                <div class=\"boa32-card\">\n\n                    <div class=\"boa32-card-title\">\n                        C\u00f4ng c\u1ee5 l\u00e0m m\u1edd\n                    <\/div>\n\n                    <div class=\"boa32-tool-grid\">\n\n                        <button\n                            type=\"button\"\n                            class=\"boa32-tool active\"\n                            data-mode=\"brush\"\n                        >\n                            <b>\ud83d\udd8c\ufe0f<\/b>\n                            <span>B\u00fat<\/span>\n                        <\/button>\n\n                        <button\n                            type=\"button\"\n                            class=\"boa32-tool\"\n                            data-mode=\"rectangle\"\n                        >\n                            <b>\u25ad<\/b>\n                            <span>Ch\u1eef nh\u1eadt<\/span>\n                        <\/button>\n\n                        <button\n                            type=\"button\"\n                            class=\"boa32-tool\"\n                            data-mode=\"circle\"\n                        >\n                            <b>\u2b55<\/b>\n                            <span>H\u00ecnh tr\u00f2n<\/span>\n                        <\/button>\n\n                        <button\n                            type=\"button\"\n                            class=\"boa32-tool\"\n                            data-mode=\"mosaic\"\n                        >\n                            <b>\ud83d\udd32<\/b>\n                            <span>Mosaic<\/span>\n                        <\/button>\n\n                        <button\n                            type=\"button\"\n                            class=\"boa32-tool\"\n                            data-mode=\"full\"\n                        >\n                            <b>\ud83c\udf2b\ufe0f<\/b>\n                            <span>To\u00e0n \u1ea3nh<\/span>\n                        <\/button>\n\n                        <button\n                            type=\"button\"\n                            class=\"boa32-tool\"\n                            data-mode=\"erase\"\n                        >\n                            <b>\ud83e\uddfd<\/b>\n                            <span>T\u1ea9y<\/span>\n                        <\/button>\n\n                    <\/div>\n\n                    <div\n                        class=\"boa32-help\"\n                        data-help\n                    >\n                        \ud83d\udd8c\ufe0f T\u00f4 l\u00ean v\u00f9ng mu\u1ed1n l\u00e0m m\u1edd.\n                    <\/div>\n\n                <\/div>\n\n\n                <!-- ==================================================\n                     SETTINGS\n                =================================================== -->\n\n                <div class=\"boa32-card\">\n\n                    <div class=\"boa32-card-title\">\n                        \u0110i\u1ec1u ch\u1ec9nh\n                    <\/div>\n\n                    <div class=\"boa32-control\">\n\n                        <label>\n\n                            <span>\n                                \u0110\u1ed9 m\u1edd\n                            <\/span>\n\n                            <strong data-blur-label>\n                                12\n                            <\/strong>\n\n                        <\/label>\n\n                        <input\n                            type=\"range\"\n                            min=\"1\"\n                            max=\"45\"\n                            value=\"12\"\n                            data-blur\n                        >\n\n                    <\/div>\n\n\n                    <div\n                        class=\"boa32-control\"\n                        data-brush-control\n                    >\n\n                        <label>\n\n                            <span>\n                                K\u00edch th\u01b0\u1edbc b\u00fat\n                            <\/span>\n\n                            <strong data-brush-label>\n                                60\n                            <\/strong>\n\n                        <\/label>\n\n                        <input\n                            type=\"range\"\n                            min=\"10\"\n                            max=\"250\"\n                            value=\"60\"\n                            data-brush\n                        >\n\n                    <\/div>\n\n                <\/div>\n\n\n                <!-- ==================================================\n                     AI\n                =================================================== -->\n\n                <div class=\"boa32-card boa32-ai\">\n\n                    <div class=\"boa32-ai-title\">\n\n                        <div>\n\n                            <strong>\n                                \ud83e\udd16 AI nh\u1eadn di\u1ec7n\n                            <\/strong>\n\n                            <span>\n                                T\u1ef1 \u0111\u1ed9ng t\u00ecm khu\u00f4n m\u1eb7t v\u00e0 v\u1eadt th\u1ec3\n                            <\/span>\n\n                        <\/div>\n\n                        <em data-ai-status>\n                            Ch\u01b0a ch\u1ea1y\n                        <\/em>\n\n                    <\/div>\n\n\n                    <!-- FACE -->\n\n                    <div class=\"boa32-ai-section\">\n\n                        <div class=\"boa32-ai-section-title\">\n                            \ud83d\udc64 Khu\u00f4n m\u1eb7t\n                        <\/div>\n\n                        <label class=\"boa32-check-large\">\n\n                            <input\n                                type=\"checkbox\"\n                                data-face\n                                checked\n                            >\n\n                            <span>\n                                T\u1ef1 \u0111\u1ed9ng t\u00ecm v\u00e0 l\u00e0m m\u1edd khu\u00f4n m\u1eb7t\n                            <\/span>\n\n                        <\/label>\n\n                    <\/div>\n\n\n                    <!-- OBJECT SEARCH -->\n\n                    <div class=\"boa32-ai-section\">\n\n                        <div class=\"boa32-ai-section-title\">\n\n                            \ud83d\udce6 V\u1eadt th\u1ec3\n\n                            <button\n                                type=\"button\"\n                                data-select-all\n                            >\n                                Ch\u1ecdn t\u1ea5t c\u1ea3\n                            <\/button>\n\n                        <\/div>\n\n\n                        <input\n                            type=\"search\"\n                            class=\"boa32-search\"\n                            data-object-search\n                            placeholder=\"\ud83d\udd0e T\u00ecm v\u1eadt th\u1ec3...\"\n                        >\n\n\n                        <div\n                            class=\"boa32-object-list\"\n                            data-object-list\n                        >\n\n                            <!-- JS t\u1ef1 t\u1ea1o danh s\u00e1ch -->\n\n                        <\/div>\n\n                    <\/div>\n\n\n                    <!-- AI SETTINGS -->\n\n                    <div class=\"boa32-ai-settings\">\n\n                        <div>\n\n                            <label>\n                                \u0110\u1ed9 tin c\u1eady AI\n                            <\/label>\n\n                            <strong data-confidence-label>\n                                50%\n                            <\/strong>\n\n                        <\/div>\n\n                        <input\n                            type=\"range\"\n                            min=\"25\"\n                            max=\"90\"\n                            value=\"50\"\n                            data-confidence\n                        >\n\n\n                        <div>\n\n                            <label>\n                                Ki\u1ec3u l\u00e0m m\u1edd AI\n                            <\/label>\n\n                            <select data-ai-effect>\n\n                                <option value=\"blur\">\n                                    \ud83c\udf2b\ufe0f Blur\n                                <\/option>\n\n                                <option value=\"mosaic\">\n                                    \ud83d\udd32 Mosaic\n                                <\/option>\n\n                            <\/select>\n\n                        <\/div>\n\n                    <\/div>\n\n\n                    <!-- AI BUTTON -->\n\n                    <button\n                        type=\"button\"\n                        class=\"boa32-ai-button\"\n                        data-ai-button\n                    >\n                        \ud83e\udd16 AI t\u00ecm v\u00e0 l\u00e0m m\u1edd\n                    <\/button>\n\n\n                    <!-- LOADING -->\n\n                    <div\n                        class=\"boa32-ai-loading\"\n                        data-ai-loading\n                        hidden\n                    >\n\n                        <div class=\"boa32-spinner\"><\/div>\n\n                        <div>\n\n                            <strong data-ai-loading-title>\n                                \u0110ang chu\u1ea9n b\u1ecb AI...\n                            <\/strong>\n\n                            <span data-ai-loading-text>\n                                Vui l\u00f2ng ch\u1edd.\n                            <\/span>\n\n                        <\/div>\n\n                    <\/div>\n\n\n                    <!-- RESULTS -->\n\n                    <div\n                        class=\"boa32-ai-results\"\n                        data-ai-results\n                    ><\/div>\n\n                <\/div>\n\n\n                <!-- ==================================================\n                     HISTORY\n                =================================================== -->\n\n                <div class=\"boa32-card\">\n\n                    <div class=\"boa32-card-title\">\n                        L\u1ecbch s\u1eed\n                    <\/div>\n\n                    <div class=\"boa32-history\">\n\n                        <button type=\"button\" data-undo>\n                            \u21b6 Ho\u00e0n t\u00e1c\n                        <\/button>\n\n                        <button type=\"button\" data-redo>\n                            \u21b7 L\u00e0m l\u1ea1i\n                        <\/button>\n\n                        <button type=\"button\" data-reset>\n                            \u27f3 \u0110\u1eb7t l\u1ea1i\n                        <\/button>\n\n                    <\/div>\n\n                <\/div>\n\n\n                <!-- ==================================================\n                     DOWNLOAD\n                =================================================== -->\n\n                <div class=\"boa32-download\">\n\n                    <strong>\n                        Ho\u00e0n t\u1ea5t\n                    <\/strong>\n\n                    <span>\n                        Ch\u1ecdn \u0111\u1ecbnh d\u1ea1ng \u1ea3nh r\u1ed3i t\u1ea3i xu\u1ed1ng.\n                    <\/span>\n\n\n                    <div class=\"boa32-download-row\">\n\n                        <select\n                            data-format\n                            aria-label=\"\u0110\u1ecbnh d\u1ea1ng \u1ea3nh\"\n                        >\n\n                            <option value=\"image\/png\">\n                                PNG\n                            <\/option>\n\n                            <option value=\"image\/jpeg\">\n                                JPG\n                            <\/option>\n\n                            <option value=\"image\/webp\">\n                                WebP\n                            <\/option>\n\n                        <\/select>\n\n\n                        <button\n                            type=\"button\"\n                            data-download\n                        >\n                            \u2b07 T\u1ea3i \u1ea3nh\n                        <\/button>\n\n                    <\/div>\n\n                <\/div>\n\n\n                <div\n                    class=\"boa32-status\"\n                    data-status\n                ><\/div>\n\n            <\/div>\n\n        <\/div>\n\n    <\/div>\n\n<\/div>\n\n\n<style>\n\n\/* ============================================================\n   ROOT\n============================================================ *\/\n\n#boa32_348565 {\n\n    --b-primary:#2563eb;\n    --b-primary-dark:#1d4ed8;\n    --b-border:#e2e8f0;\n    --b-text:#0f172a;\n    --b-muted:#64748b;\n\n    width:100%;\n\n    font-family:\n        -apple-system,\n        BlinkMacSystemFont,\n        \"Segoe UI\",\n        Roboto,\n        Arial,\n        sans-serif;\n}\n\n\n#boa32_348565 * {\n    box-sizing:border-box;\n}\n\n\n\/* ============================================================\n   UPLOAD\n============================================================ *\/\n\n#boa32_348565 .boa32-upload {\n\n    min-height:430px;\n\n    border:2px dashed #cbd5e1;\n\n    border-radius:18px;\n\n    background:#f8fafc;\n\n    display:flex;\n\n    flex-direction:column;\n\n    justify-content:center;\n\n    align-items:center;\n\n    text-align:center;\n\n    padding:30px;\n}\n\n\n#boa32_348565 .boa32-upload-icon {\n\n    font-size:58px;\n    margin-bottom:10px;\n}\n\n\n#boa32_348565 .boa32-upload h2 {\n\n    margin:0 0 7px;\n\n    color:#0f172a !important;\n}\n\n\n#boa32_348565 .boa32-upload p {\n\n    margin:0 0 18px;\n\n    color:#64748b !important;\n}\n\n\n#boa32_348565 .boa32-upload small {\n\n    margin-top:12px;\n\n    color:#94a3b8 !important;\n}\n\n\n\/* ============================================================\n   BUTTON\n============================================================ *\/\n\n#boa32_348565 button {\n\n    font-family:inherit;\n}\n\n\n#boa32_348565 .boa32-primary {\n\n    border:0 !important;\n\n    background:#2563eb !important;\n\n    color:#ffffff !important;\n\n    border-radius:9px !important;\n\n    padding:11px 20px !important;\n\n    font-weight:600 !important;\n\n    cursor:pointer;\n}\n\n\n\/* ============================================================\n   EDITOR\n============================================================ *\/\n\n#boa32_348565 .boa32-editor {\n\n    display:grid;\n\n    grid-template-columns:\n        minmax(0,1fr)\n        minmax(370px,440px);\n\n    height:\n        calc(100vh - 40px);\n\n    min-height:600px;\n\n    gap:20px;\n}\n\n\n\/* ============================================================\n   IMAGE\n============================================================ *\/\n\n#boa32_348565 .boa32-image-column {\n\n    min-width:0;\n\n    height:100%;\n}\n\n\n#boa32_348565 .boa32-image-sticky {\n\n    position:sticky;\n\n    position:-webkit-sticky;\n\n    top:20px;\n\n    height:\n        calc(100vh - 40px);\n\n    display:flex;\n\n    flex-direction:column;\n}\n\n\n#boa32_348565 .boa32-image-header {\n\n    flex:0 0 auto;\n\n    display:flex;\n\n    justify-content:space-between;\n\n    align-items:center;\n\n    padding-bottom:10px;\n}\n\n\n#boa32_348565 .boa32-image-header > div {\n\n    display:flex;\n\n    flex-direction:column;\n\n    gap:3px;\n}\n\n\n#boa32_348565 .boa32-image-header strong {\n\n    color:#0f172a !important;\n\n    font-size:16px;\n}\n\n\n#boa32_348565 .boa32-image-header span {\n\n    color:#64748b !important;\n\n    font-size:12px;\n}\n\n\n#boa32_348565 .boa32-new {\n\n    width:38px;\n\n    height:38px;\n\n    border:1px solid #e2e8f0 !important;\n\n    background:#ffffff !important;\n\n    color:#111827 !important;\n\n    border-radius:9px !important;\n\n    cursor:pointer;\n\n    font-size:20px;\n}\n\n\n\/* ============================================================\n   CANVAS\n============================================================ *\/\n\n#boa32_348565 .boa32-canvas-wrap {\n\n    flex:1;\n\n    min-height:0;\n\n    overflow:auto;\n\n    display:flex;\n\n    justify-content:center;\n\n    align-items:center;\n\n    padding:20px;\n\n    border:1px solid #e2e8f0;\n\n    border-radius:14px;\n\n    background-color:#f8fafc;\n\n    background-image:\n        linear-gradient(45deg,#e2e8f0 25%,transparent 25%),\n        linear-gradient(-45deg,#e2e8f0 25%,transparent 25%),\n        linear-gradient(45deg,transparent 75%,#e2e8f0 75%),\n        linear-gradient(-45deg,transparent 75%,#e2e8f0 75%);\n\n    background-size:22px 22px;\n\n    background-position:\n        0 0,\n        0 11px,\n        11px -11px,\n        -11px 0;\n}\n\n\n#boa32_348565 canvas {\n\n    display:block;\n\n    max-width:none !important;\n\n    touch-action:none;\n\n    box-shadow:\n        0 8px 30px rgba(15,23,42,.18);\n\n    cursor:crosshair;\n}\n\n\n\/* ============================================================\n   FOOTER\n============================================================ *\/\n\n#boa32_348565 .boa32-image-footer {\n\n    flex:0 0 auto;\n\n    height:45px;\n\n    display:flex;\n\n    justify-content:center;\n\n    align-items:center;\n\n    gap:10px;\n}\n\n\n#boa32_348565 .boa32-zoom {\n\n    display:flex;\n\n    gap:5px;\n}\n\n\n#boa32_348565 .boa32-zoom button {\n\n    width:38px;\n\n    height:34px;\n\n    border:1px solid #e2e8f0 !important;\n\n    background:#ffffff !important;\n\n    color:#111827 !important;\n\n    border-radius:7px !important;\n\n    cursor:pointer;\n}\n\n\n#boa32_348565 .boa32-image-footer > span {\n\n    color:#64748b !important;\n\n    font-size:12px;\n}\n\n\n\/* ============================================================\n   RIGHT SCROLL\n============================================================ *\/\n\n#boa32_348565 .boa32-tools-column {\n\n    height:100%;\n\n    min-height:0;\n\n    overflow-y:auto;\n\n    overflow-x:hidden;\n\n    overscroll-behavior:contain;\n\n    scrollbar-width:thin;\n}\n\n\n#boa32_348565 .boa32-tools-inner {\n\n    padding-right:5px;\n\n    padding-bottom:30px;\n}\n\n\n\/* ============================================================\n   HEADING\n============================================================ *\/\n\n#boa32_348565 .boa32-heading {\n\n    padding-bottom:14px;\n}\n\n\n#boa32_348565 .boa32-heading h2 {\n\n    margin:0 0 4px;\n\n    color:#0f172a !important;\n\n    font-size:22px;\n}\n\n\n#boa32_348565 .boa32-heading p {\n\n    margin:0 0 8px;\n\n    color:#64748b !important;\n\n    font-size:13px;\n}\n\n\n#boa32_348565 .boa32-heading span {\n\n    display:inline-block;\n\n    padding:6px 9px;\n\n    border-radius:999px;\n\n    background:#f0fdf4 !important;\n\n    color:#15803d !important;\n\n    font-size:10px;\n}\n\n\n\/* ============================================================\n   CARD\n============================================================ *\/\n\n#boa32_348565 .boa32-card {\n\n    background:#ffffff !important;\n\n    border:1px solid #e2e8f0;\n\n    border-radius:12px;\n\n    padding:15px;\n\n    margin-bottom:12px;\n}\n\n\n#boa32_348565 .boa32-card-title {\n\n    color:#0f172a !important;\n\n    font-size:14px;\n\n    font-weight:700;\n\n    margin-bottom:12px;\n}\n\n\n\/* ============================================================\n   TOOLS\n============================================================ *\/\n\n#boa32_348565 .boa32-tool-grid {\n\n    display:grid;\n\n    grid-template-columns:\n        1fr 1fr;\n\n    gap:7px;\n}\n\n\n#boa32_348565 .boa32-tool {\n\n    min-height:63px;\n\n    border:1px solid #e2e8f0 !important;\n\n    background:#ffffff !important;\n\n    color:#334155 !important;\n\n    border-radius:9px !important;\n\n    cursor:pointer;\n\n    display:flex;\n\n    flex-direction:column;\n\n    justify-content:center;\n\n    align-items:center;\n\n    gap:4px;\n}\n\n\n#boa32_348565 .boa32-tool b {\n\n    font-size:19px;\n}\n\n\n#boa32_348565 .boa32-tool span {\n\n    color:inherit !important;\n\n    font-size:11px;\n}\n\n\n#boa32_348565 .boa32-tool.active {\n\n    background:#eff6ff !important;\n\n    border-color:#93c5fd !important;\n\n    color:#1d4ed8 !important;\n}\n\n\n#boa32_348565 .boa32-help {\n\n    margin-top:10px;\n\n    padding:9px;\n\n    border-radius:8px;\n\n    background:#eff6ff !important;\n\n    border:1px solid #dbeafe !important;\n\n    color:#1e40af !important;\n\n    font-size:11px;\n}\n\n\n\/* ============================================================\n   RANGE\n============================================================ *\/\n\n#boa32_348565 .boa32-control {\n\n    margin-bottom:15px;\n}\n\n\n#boa32_348565 .boa32-control:last-child {\n\n    margin-bottom:0;\n}\n\n\n#boa32_348565 .boa32-control label {\n\n    display:flex;\n\n    justify-content:space-between;\n\n    margin-bottom:7px;\n\n    font-size:12px;\n\n    color:#475569 !important;\n}\n\n\n#boa32_348565 .boa32-control strong {\n\n    color:#2563eb !important;\n}\n\n\n#boa32_348565 input[type=\"range\"] {\n\n    width:100%;\n\n    accent-color:#2563eb;\n}\n\n\n\/* ============================================================\n   AI\n============================================================ *\/\n\n#boa32_348565 .boa32-ai {\n\n    background:#f8fafc !important;\n}\n\n\n#boa32_348565 .boa32-ai-title {\n\n    display:flex;\n\n    justify-content:space-between;\n\n    gap:10px;\n\n    margin-bottom:13px;\n}\n\n\n#boa32_348565 .boa32-ai-title > div {\n\n    display:flex;\n\n    flex-direction:column;\n\n    gap:3px;\n}\n\n\n#boa32_348565 .boa32-ai-title strong {\n\n    color:#111827 !important;\n\n    font-size:14px;\n}\n\n\n#boa32_348565 .boa32-ai-title span {\n\n    color:#64748b !important;\n\n    font-size:10px;\n}\n\n\n#boa32_348565 .boa32-ai-title em {\n\n    font-style:normal;\n\n    white-space:nowrap;\n\n    height:max-content;\n\n    padding:5px 7px;\n\n    border-radius:999px;\n\n    background:#ffffff !important;\n\n    border:1px solid #e2e8f0;\n\n    color:#334155 !important;\n\n    font-size:9px;\n}\n\n\n\/* ============================================================\n   AI SECTION\n============================================================ *\/\n\n#boa32_348565 .boa32-ai-section {\n\n    margin-bottom:13px;\n}\n\n\n#boa32_348565 .boa32-ai-section-title {\n\n    display:flex;\n\n    justify-content:space-between;\n\n    align-items:center;\n\n    margin-bottom:7px;\n\n    color:#334155 !important;\n\n    font-size:11px;\n\n    font-weight:700;\n}\n\n\n#boa32_348565 .boa32-ai-section-title button {\n\n    border:0 !important;\n\n    background:transparent !important;\n\n    color:#2563eb !important;\n\n    font-size:9px;\n\n    cursor:pointer;\n\n    padding:2px 4px !important;\n}\n\n\n#boa32_348565 .boa32-check-large {\n\n    display:flex;\n\n    align-items:center;\n\n    gap:8px;\n\n    padding:9px;\n\n    background:#ffffff !important;\n\n    border:1px solid #e2e8f0;\n\n    border-radius:8px;\n\n    cursor:pointer;\n\n    color:#334155 !important;\n\n    font-size:10px;\n}\n\n\n#boa32_348565 .boa32-check-large input {\n\n    margin:0;\n\n    accent-color:#2563eb;\n}\n\n\n\/* ============================================================\n   SEARCH\n============================================================ *\/\n\n#boa32_348565 .boa32-search {\n\n    width:100%;\n\n    height:36px;\n\n    border:1px solid #cbd5e1 !important;\n\n    background:#ffffff !important;\n\n    color:#111827 !important;\n\n    border-radius:7px !important;\n\n    padding:0 9px !important;\n\n    margin-bottom:7px;\n\n    font-size:11px !important;\n}\n\n\n#boa32_348565 .boa32-search::placeholder {\n\n    color:#94a3b8 !important;\n}\n\n\n\/* ============================================================\n   OBJECT LIST\n============================================================ *\/\n\n#boa32_348565 .boa32-object-list {\n\n    max-height:290px;\n\n    overflow-y:auto;\n\n    display:grid;\n\n    grid-template-columns:\n        1fr 1fr;\n\n    gap:5px;\n\n    padding-right:3px;\n}\n\n\n#boa32_348565 .boa32-object-item {\n\n    display:flex;\n\n    align-items:center;\n\n    gap:5px;\n\n    min-width:0;\n\n    padding:7px;\n\n    background:#ffffff !important;\n\n    border:1px solid #e2e8f0;\n\n    border-radius:7px;\n\n    color:#334155 !important;\n\n    font-size:9px;\n\n    cursor:pointer;\n}\n\n\n#boa32_348565 .boa32-object-item:hover {\n\n    border-color:#93c5fd;\n}\n\n\n#boa32_348565 .boa32-object-item input {\n\n    margin:0;\n\n    accent-color:#2563eb;\n}\n\n\n#boa32_348565 .boa32-object-item span {\n\n    overflow:hidden;\n\n    text-overflow:ellipsis;\n\n    white-space:nowrap;\n\n    color:#334155 !important;\n}\n\n\n\/* ============================================================\n   AI SETTINGS\n============================================================ *\/\n\n#boa32_348565 .boa32-ai-settings {\n\n    padding:10px;\n\n    margin-bottom:10px;\n\n    background:#ffffff !important;\n\n    border:1px solid #e2e8f0;\n\n    border-radius:8px;\n}\n\n\n#boa32_348565 .boa32-ai-settings > div:first-child {\n\n    display:flex;\n\n    justify-content:space-between;\n\n    margin-bottom:6px;\n}\n\n\n#boa32_348565 .boa32-ai-settings label {\n\n    color:#475569 !important;\n\n    font-size:10px;\n}\n\n\n#boa32_348565 .boa32-ai-settings strong {\n\n    color:#2563eb !important;\n\n    font-size:10px;\n}\n\n\n#boa32_348565 .boa32-ai-settings select {\n\n    width:100%;\n\n    height:34px;\n\n    margin-top:6px;\n\n    border:1px solid #cbd5e1 !important;\n\n    border-radius:7px !important;\n\n    background:#ffffff !important;\n\n    color:#111827 !important;\n\n    font-size:10px !important;\n\n    padding:0 7px !important;\n}\n\n\n\/* ============================================================\n   AI BUTTON\n============================================================ *\/\n\n#boa32_348565 .boa32-ai-button {\n\n    width:100%;\n\n    padding:11px;\n\n    border:0 !important;\n\n    border-radius:9px !important;\n\n    background:#111827 !important;\n\n    color:#ffffff !important;\n\n    font-weight:600 !important;\n\n    cursor:pointer;\n}\n\n\n#boa32_348565 .boa32-ai-button:disabled {\n\n    opacity:.55;\n\n    cursor:wait;\n}\n\n\n\/* ============================================================\n   AI LOADING\n============================================================ *\/\n\n#boa32_348565 .boa32-ai-loading {\n\n    margin-top:9px;\n\n    padding:10px;\n\n    border-radius:8px;\n\n    background:#eff6ff !important;\n\n    border:1px solid #dbeafe !important;\n\n    display:flex;\n\n    gap:9px;\n\n    align-items:center;\n}\n\n\n#boa32_348565 .boa32-spinner {\n\n    width:23px;\n\n    height:23px;\n\n    flex:0 0 auto;\n\n    border:3px solid #bfdbfe;\n\n    border-top-color:#2563eb;\n\n    border-radius:50%;\n\n    animation:\n        boa32spin .8s linear infinite;\n}\n\n\n@keyframes boa32spin {\n\n    to {\n        transform:rotate(360deg);\n    }\n\n}\n\n\n#boa32_348565 .boa32-ai-loading strong {\n\n    display:block;\n\n    color:#1e3a8a !important;\n\n    font-size:11px;\n}\n\n\n#boa32_348565 .boa32-ai-loading span {\n\n    display:block;\n\n    color:#64748b !important;\n\n    font-size:9px;\n\n    margin-top:2px;\n}\n\n\n\/* ============================================================\n   AI RESULTS\n============================================================ *\/\n\n#boa32_348565 .boa32-ai-results {\n\n    display:flex;\n\n    flex-wrap:wrap;\n\n    gap:5px;\n\n    margin-top:8px;\n}\n\n\n#boa32_348565 .boa32-ai-tag {\n\n    padding:5px 7px;\n\n    background:#ffffff !important;\n\n    border:1px solid #e2e8f0;\n\n    border-radius:6px;\n\n    color:#334155 !important;\n\n    font-size:9px;\n}\n\n\n\/* ============================================================\n   HISTORY\n============================================================ *\/\n\n#boa32_348565 .boa32-history {\n\n    display:grid;\n\n    grid-template-columns:\n        1fr 1fr 1fr;\n\n    gap:6px;\n}\n\n\n#boa32_348565 .boa32-history button {\n\n    padding:9px 5px;\n\n    border:1px solid #e2e8f0 !important;\n\n    background:#ffffff !important;\n\n    color:#334155 !important;\n\n    border-radius:7px !important;\n\n    cursor:pointer;\n\n    font-size:10px;\n}\n\n\n\/* ============================================================\n   DOWNLOAD\n============================================================ *\/\n\n#boa32_348565 .boa32-download {\n\n    padding:15px;\n\n    border-radius:12px;\n\n    background:#111827 !important;\n\n    color:#ffffff !important;\n}\n\n\n#boa32_348565 .boa32-download strong {\n\n    display:block;\n\n    color:#ffffff !important;\n\n    font-size:14px;\n}\n\n\n#boa32_348565 .boa32-download > span {\n\n    display:block;\n\n    color:#cbd5e1 !important;\n\n    font-size:10px;\n\n    margin-top:3px;\n\n    margin-bottom:10px;\n}\n\n\n#boa32_348565 .boa32-download-row {\n\n    display:flex;\n\n    gap:7px;\n}\n\n\n#boa32_348565 .boa32-download select {\n\n    width:90px !important;\n\n    min-width:90px !important;\n\n    height:42px !important;\n\n    border:1px solid #cbd5e1 !important;\n\n    border-radius:7px !important;\n\n    background:#ffffff !important;\n\n    color:#111827 !important;\n\n    -webkit-text-fill-color:#111827 !important;\n\n    padding:0 10px !important;\n\n    font-family:inherit !important;\n\n    font-size:13px !important;\n\n    font-weight:600 !important;\n\n    cursor:pointer !important;\n}\n\n\n#boa32_348565 .boa32-download select option {\n\n    background:#ffffff !important;\n\n    color:#111827 !important;\n\n}\n\n\n#boa32_348565 .boa32-download button {\n\n    flex:1;\n\n    min-height:42px;\n\n    border:0 !important;\n\n    border-radius:7px !important;\n\n    background:#16a34a !important;\n\n    color:#ffffff !important;\n\n    -webkit-text-fill-color:#ffffff !important;\n\n    font-size:13px !important;\n\n    font-weight:600 !important;\n\n    cursor:pointer !important;\n}\n\n\n\/* ============================================================\n   STATUS\n============================================================ *\/\n\n#boa32_348565 .boa32-status {\n\n    min-height:25px;\n\n    text-align:center;\n\n    padding:7px;\n\n    color:#64748b !important;\n\n    font-size:10px;\n}\n\n\n\/* ============================================================\n   MOBILE\n============================================================ *\/\n\n\/* ============================================================\n   MOBILE SAFETY\n============================================================ *\/\n\n#boa32_348565 .boa32-canvas-wrap {\n    -webkit-overflow-scrolling:touch;\n    overscroll-behavior:contain;\n}\n\n#boa32_348565 canvas {\n    -webkit-user-select:none;\n    user-select:none;\n    -webkit-touch-callout:none;\n    touch-action:none !important;\n}\n\n@media(max-width:850px){\n\n    #boa32_348565 .boa32-editor {\n        display:block;\n        height:auto;\n        min-height:0;\n    }\n\n    #boa32_348565 .boa32-image-sticky {\n        position:relative;\n        top:auto;\n        height:auto;\n    }\n\n    \/*\n     * MOBILE CANVAS: lu\u00f4n d\u00f9ng k\u00edch th\u01b0\u1edbc hi\u1ec3n th\u1ecb theo CSS c\u1ee7a v\u00f9ng ch\u1ee9a.\n     * Rule desktop c\u00f3 max-width:none!important n\u00ean ph\u1ea3i ghi \u0111\u00e8 b\u1eb1ng !important.\n     * Kh\u00f4ng \u00e9p canvas cao theo viewport; chi\u1ec1u cao s\u1ebd t\u1ef1 theo t\u1ec9 l\u1ec7 \u1ea3nh.\n     *\/\n    #boa32_348565 .boa32-canvas-wrap {\n        width:100%;\n        height:auto;\n        min-height:0;\n        max-height:none;\n        overflow:auto;\n        padding:10px;\n        display:flex;\n        justify-content:center;\n        align-items:flex-start;\n        box-sizing:border-box;\n    }\n\n    #boa32_348565 .boa32-canvas-wrap canvas {\n        display:block;\n        margin:0 auto;\n        flex:0 0 auto;\n        max-width:none !important;\n        max-height:none !important;\n    }\n\n\n    #boa32_348565 .boa32-tools-column {\n\n        height:auto;\n\n        overflow:visible;\n    }\n\n\n    #boa32_348565 .boa32-tools-inner {\n\n        padding-right:0;\n    }\n\n}\n\n\n\/* ============================================================\n   SMALL MOBILE\n============================================================ *\/\n\n@media(max-width:480px){\n\n    #boa32_348565 .boa32-object-list {\n        grid-template-columns:1fr 1fr;\n    }\n\n    #boa32_348565 .boa32-canvas-wrap {\n        height:auto;\n        min-height:0;\n        max-height:none;\n        padding:8px;\n    }\n\n\n    #boa32_348565 .boa32-download select {\n\n        width:82px !important;\n\n        min-width:82px !important;\n    }\n\n}\n\n<\/style>\n\n\n<script>\n\n(function(){\n\n'use strict';\n\n\n\/* ============================================================\n   ROOT\n============================================================ *\/\n\nconst root =\n    document.getElementById(\n        \"boa32_348565\"    );\n\n\nif(!root){\n    return;\n}\n\n\n\/* ============================================================\n   ELEMENTS\n============================================================ *\/\n\nconst upload =\n    root.querySelector('[data-upload]');\n\nconst editor =\n    root.querySelector('[data-editor]');\n\nconst fileInput =\n    root.querySelector('[data-file]');\n\nconst selectButton =\n    root.querySelector('[data-select]');\n\nconst canvas =\n    root.querySelector('[data-canvas]');\n\nconst ctx =\n    canvas.getContext('2d');\n\n\nconst originalCanvas =\n    document.createElement('canvas');\n\nconst originalCtx =\n    originalCanvas.getContext('2d');\n\n\n\/*\n * Mask l\u00e0m m\u1edd b\u1eb1ng b\u00fat \/ h\u00ecnh.\n *\/\n\nconst blurMaskCanvas =\n    document.createElement('canvas');\n\nconst blurMaskCtx =\n    blurMaskCanvas.getContext('2d');\n\n\n\/*\n * Mask Mosaic.\n *\/\n\nconst mosaicMaskCanvas =\n    document.createElement('canvas');\n\nconst mosaicMaskCtx =\n    mosaicMaskCanvas.getContext('2d');\n\n\n\/*\n * Mask AI Blur.\n *\/\n\nconst aiBlurMaskCanvas =\n    document.createElement('canvas');\n\nconst aiBlurMaskCtx =\n    aiBlurMaskCanvas.getContext('2d');\n\n\n\/*\n * Mask AI Mosaic.\n *\/\n\nconst aiMosaicMaskCanvas =\n    document.createElement('canvas');\n\nconst aiMosaicMaskCtx =\n    aiMosaicMaskCanvas.getContext('2d');\n\n\n\/*\n * Canvas hi\u1ec7u \u1ee9ng.\n *\/\n\nconst blurCanvas =\n    document.createElement('canvas');\n\nconst blurCtx =\n    blurCanvas.getContext('2d');\n\n\nconst mosaicCanvas =\n    document.createElement('canvas');\n\nconst mosaicCtx =\n    mosaicCanvas.getContext('2d');\n\nconst blurSmallCanvas =\n    document.createElement('canvas');\n\nconst blurSmallCtx =\n    blurSmallCanvas.getContext('2d');\n\n\n\/* ============================================================\n   DOM\n============================================================ *\/\n\nconst sizeLabel =\n    root.querySelector('[data-size]');\n\nconst status =\n    root.querySelector('[data-status]');\n\nconst help =\n    root.querySelector('[data-help]');\n\nconst blurRange =\n    root.querySelector('[data-blur]');\n\nconst blurLabel =\n    root.querySelector('[data-blur-label]');\n\nconst brushRange =\n    root.querySelector('[data-brush]');\n\nconst brushLabel =\n    root.querySelector('[data-brush-label]');\n\nconst brushControl =\n    root.querySelector('[data-brush-control]');\n\nconst toolButtons =\n    root.querySelectorAll('[data-mode]');\n\n\nconst undoButton =\n    root.querySelector('[data-undo]');\n\nconst redoButton =\n    root.querySelector('[data-redo]');\n\nconst resetButton =\n    root.querySelector('[data-reset]');\n\nconst newButton =\n    root.querySelector('[data-new]');\n\n\nconst zoomIn =\n    root.querySelector('[data-zoom-in]');\n\nconst zoomOut =\n    root.querySelector('[data-zoom-out]');\n\nconst zoomReset =\n    root.querySelector('[data-zoom-reset]');\n\nconst zoomLabel =\n    root.querySelector('[data-zoom-label]');\n\n\n\/* ============================================================\n   AI ELEMENTS\n============================================================ *\/\n\nconst faceCheckbox =\n    root.querySelector('[data-face]');\n\nconst objectList =\n    root.querySelector('[data-object-list]');\n\nconst objectSearch =\n    root.querySelector('[data-object-search]');\n\nconst selectAllObjects =\n    root.querySelector('[data-select-all]');\n\nconst confidence =\n    root.querySelector('[data-confidence]');\n\nconst confidenceLabel =\n    root.querySelector('[data-confidence-label]');\n\nconst aiEffect =\n    root.querySelector('[data-ai-effect]');\n\nconst aiButton =\n    root.querySelector('[data-ai-button]');\n\nconst aiStatus =\n    root.querySelector('[data-ai-status]');\n\nconst aiLoading =\n    root.querySelector('[data-ai-loading]');\n\nconst aiLoadingTitle =\n    root.querySelector('[data-ai-loading-title]');\n\nconst aiLoadingText =\n    root.querySelector('[data-ai-loading-text]');\n\nconst aiResults =\n    root.querySelector('[data-ai-results]');\n\n\nconst formatSelect =\n    root.querySelector('[data-format]');\n\nconst downloadButton =\n    root.querySelector('[data-download]');\n\n\n\/* ============================================================\n   STATE\n============================================================ *\/\n\nlet image=null;\n\nlet mode='brush';\n\nlet blurAmount=12;\n\nlet brushSize=60;\n\nlet zoom=1;\n\nlet drawing=false;\n\nlet startPoint=null;\n\nlet lastPoint=null;\n\n\n\/*\n * FIX QUAN TR\u1eccNG:\n *\n * Full image ch\u1ec9 l\u00e0 m\u1ed9t tr\u1ea1ng th\u00e1i ri\u00eang.\n *\n * Khi ch\u1ecdn c\u00f4ng c\u1ee5 kh\u00e1c:\n *\n * fullImageBlur = false\n *\n * n\u00ean kh\u00f4ng c\u00f2n l\u1ed7i:\n *\n * To\u00e0n \u1ea3nh \u2192 B\u00fat\n *\n * nh\u01b0ng to\u00e0n b\u1ed9 \u1ea3nh v\u1eabn b\u1ecb blur.\n *\/\n\nlet fullImageBlur=false;\n\n\nlet history=[];\n\nlet historyIndex=-1;\n\n\n\/* AI models *\/\n\nlet cocoModel=null;\n\nlet faceModel=null;\n\nlet aiLoadPromise=null;\n\nlet aiRunning=false;\n\nlet renderFramePending=false;\n\nlet renderRequestedAgain=false;\n\n\nconst MAX_FILE_SIZE =\n    20*1024*1024;\n\n\nconst IS_MOBILE_DEVICE = \/Android|iPhone|iPad|iPod|Mobile\/i.test(navigator.userAgent) || (navigator.maxTouchPoints > 1 && window.innerWidth < 900);\n\nconst MAX_IMAGE_SIZE =\n    IS_MOBILE_DEVICE ? 1400 : 3000;\n\n\n\/* ============================================================\n   40 OBJECTS\n============================================================ *\/\n\nconst OBJECTS = [\n\n    {\n        id:'person',\n        icon:'\ud83d\udc64',\n        name:'Ng\u01b0\u1eddi'\n    },\n\n    {\n        id:'bicycle',\n        icon:'\ud83d\udeb2',\n        name:'Xe \u0111\u1ea1p'\n    },\n\n    {\n        id:'car',\n        icon:'\ud83d\ude97',\n        name:'\u00d4 t\u00f4'\n    },\n\n    {\n        id:'motorcycle',\n        icon:'\ud83c\udfcd\ufe0f',\n        name:'Xe m\u00e1y'\n    },\n\n    {\n        id:'airplane',\n        icon:'\u2708\ufe0f',\n        name:'M\u00e1y bay'\n    },\n\n    {\n        id:'bus',\n        icon:'\ud83d\ude8c',\n        name:'Xe bu\u00fdt'\n    },\n\n    {\n        id:'train',\n        icon:'\ud83d\ude86',\n        name:'T\u00e0u h\u1ecfa'\n    },\n\n    {\n        id:'truck',\n        icon:'\ud83d\ude9a',\n        name:'Xe t\u1ea3i'\n    },\n\n    {\n        id:'boat',\n        icon:'\u26f5',\n        name:'Thuy\u1ec1n'\n    },\n\n    {\n        id:'traffic light',\n        icon:'\ud83d\udea6',\n        name:'\u0110\u00e8n giao th\u00f4ng'\n    },\n\n    {\n        id:'bird',\n        icon:'\ud83d\udc26',\n        name:'Chim'\n    },\n\n    {\n        id:'cat',\n        icon:'\ud83d\udc08',\n        name:'M\u00e8o'\n    },\n\n    {\n        id:'dog',\n        icon:'\ud83d\udc15',\n        name:'Ch\u00f3'\n    },\n\n    {\n        id:'horse',\n        icon:'\ud83d\udc0e',\n        name:'Ng\u1ef1a'\n    },\n\n    {\n        id:'sheep',\n        icon:'\ud83d\udc11',\n        name:'C\u1eebu'\n    },\n\n    {\n        id:'cow',\n        icon:'\ud83d\udc04',\n        name:'B\u00f2'\n    },\n\n    {\n        id:'elephant',\n        icon:'\ud83d\udc18',\n        name:'Voi'\n    },\n\n    {\n        id:'bear',\n        icon:'\ud83d\udc3b',\n        name:'G\u1ea5u'\n    },\n\n    {\n        id:'zebra',\n        icon:'\ud83e\udd93',\n        name:'Ng\u1ef1a v\u1eb1n'\n    },\n\n    {\n        id:'giraffe',\n        icon:'\ud83e\udd92',\n        name:'H\u01b0\u01a1u cao c\u1ed5'\n    },\n\n    {\n        id:'backpack',\n        icon:'\ud83c\udf92',\n        name:'Ba l\u00f4'\n    },\n\n    {\n        id:'umbrella',\n        icon:'\u2602\ufe0f',\n        name:'\u00d4 \/ D\u00f9'\n    },\n\n    {\n        id:'handbag',\n        icon:'\ud83d\udc5c',\n        name:'T\u00fai x\u00e1ch'\n    },\n\n    {\n        id:'suitcase',\n        icon:'\ud83e\uddf3',\n        name:'Vali'\n    },\n\n    {\n        id:'sports ball',\n        icon:'\u26bd',\n        name:'B\u00f3ng'\n    },\n\n    {\n        id:'baseball bat',\n        icon:'\ud83c\udfcf',\n        name:'G\u1eady b\u00f3ng ch\u00e0y'\n    },\n\n    {\n        id:'skateboard',\n        icon:'\ud83d\udef9',\n        name:'V\u00e1n tr\u01b0\u1ee3t'\n    },\n\n    {\n        id:'surfboard',\n        icon:'\ud83c\udfc4',\n        name:'V\u00e1n l\u01b0\u1edbt'\n    },\n\n    {\n        id:'bottle',\n        icon:'\ud83c\udf7e',\n        name:'Chai'\n    },\n\n    {\n        id:'wine glass',\n        icon:'\ud83c\udf77',\n        name:'Ly r\u01b0\u1ee3u'\n    },\n\n    {\n        id:'cup',\n        icon:'\u2615',\n        name:'C\u1ed1c'\n    },\n\n    {\n        id:'fork',\n        icon:'\ud83c\udf74',\n        name:'N\u0129a'\n    },\n\n    {\n        id:'knife',\n        icon:'\ud83d\udd2a',\n        name:'Dao'\n    },\n\n    {\n        id:'spoon',\n        icon:'\ud83e\udd44',\n        name:'Th\u00eca'\n    },\n\n    {\n        id:'banana',\n        icon:'\ud83c\udf4c',\n        name:'Chu\u1ed1i'\n    },\n\n    {\n        id:'apple',\n        icon:'\ud83c\udf4e',\n        name:'T\u00e1o'\n    },\n\n    {\n        id:'pizza',\n        icon:'\ud83c\udf55',\n        name:'Pizza'\n    },\n\n    {\n        id:'chair',\n        icon:'\ud83e\ude91',\n        name:'Gh\u1ebf'\n    },\n\n    {\n        id:'couch',\n        icon:'\ud83d\udecb\ufe0f',\n        name:'Sofa'\n    },\n\n    {\n        id:'laptop',\n        icon:'\ud83d\udcbb',\n        name:'Laptop'\n    },\n\n    {\n        id:'cell phone',\n        icon:'\ud83d\udcf1',\n        name:'\u0110i\u1ec7n tho\u1ea1i'\n    }\n\n];\n\n\n\/* ============================================================\n   CREATE OBJECT LIST\n============================================================ *\/\n\nfunction buildObjectList(){\n\n    objectList.innerHTML='';\n\n\n    OBJECTS.forEach(\n        function(item){\n\n            const label =\n                document.createElement(\n                    'label'\n                );\n\n\n            label.className =\n                'boa32-object-item';\n\n\n            label.dataset.object =\n                item.id;\n\n\n            label.innerHTML =\n\n                '<input '+\n                'type=\"checkbox\" '+\n                'value=\"'+\n                escapeHtml(item.id)+\n                '\" '+\n                'data-object-checkbox>'+\n\n                '<span>'+\n                item.icon+\n                ' '+\n                escapeHtml(item.name)+\n                '<\/span>';\n\n\n            objectList.appendChild(\n                label\n            );\n\n        }\n    );\n\n}\n\n\nfunction escapeHtml(text){\n\n    return String(text)\n        .replace(\n            \/&\/g,\n            '&amp;'\n        )\n        .replace(\n            \/<\/g,\n            '&lt;'\n        )\n        .replace(\n            \/>\/g,\n            '&gt;'\n        )\n        .replace(\n            \/\"\/g,\n            '&quot;'\n        )\n        .replace(\n            \/'\/g,\n            '&#039;'\n        );\n\n}\n\n\nbuildObjectList();\n\n\n\/* ============================================================\n   SEARCH OBJECTS\n============================================================ *\/\n\nobjectSearch.addEventListener(\n    'input',\n    function(){\n\n        const keyword =\n            this.value\n            .trim()\n            .toLowerCase();\n\n\n        objectList\n            .querySelectorAll(\n                '[data-object]'\n            )\n            .forEach(\n                function(item){\n\n                    const text =\n                        item.textContent\n                        .toLowerCase();\n\n\n                    item.style.display =\n                        !keyword ||\n                        text.includes(keyword)\n                            ? ''\n                            : 'none';\n\n                }\n            );\n\n    }\n);\n\n\n\/* ============================================================\n   SELECT ALL\n============================================================ *\/\n\nlet allObjectsSelected=false;\n\n\nselectAllObjects.addEventListener(\n    'click',\n    function(){\n\n        const checkboxes =\n            objectList.querySelectorAll(\n                '[data-object-checkbox]'\n            );\n\n\n        allObjectsSelected =\n            !allObjectsSelected;\n\n\n        checkboxes.forEach(\n            function(cb){\n\n                cb.checked =\n                    allObjectsSelected;\n\n            }\n        );\n\n\n        this.textContent =\n            allObjectsSelected\n                ? 'B\u1ecf ch\u1ecdn t\u1ea5t c\u1ea3'\n                : 'Ch\u1ecdn t\u1ea5t c\u1ea3';\n\n    }\n);\n\n\n\/* ============================================================\n   STATUS\n============================================================ *\/\n\nfunction setStatus(text){\n\n    status.textContent =\n        text || '';\n\n}\n\n\nfunction setAIStatus(text){\n\n    aiStatus.textContent =\n        text;\n\n}\n\n\n\/* ============================================================\n   CLAMP\n============================================================ *\/\n\nfunction clamp(\n    value,\n    min,\n    max\n){\n\n    return Math.max(\n        min,\n        Math.min(\n            max,\n            value\n        )\n    );\n\n}\n\n\n\/* ============================================================\n   FILE SELECT\n============================================================ *\/\n\nselectButton.addEventListener(\n    'click',\n    function(){\n\n        fileInput.click();\n\n    }\n);\n\n\nfileInput.addEventListener(\n    'change',\n    function(){\n\n        if(\n            this.files &&\n            this.files[0]\n        ){\n\n            loadImage(\n                this.files[0]\n            );\n\n        }\n\n    }\n);\n\n\n\/* ============================================================\n   LOAD IMAGE\n============================================================ *\/\n\nfunction loadImage(file){\n\n    if(\n        file.type &&\n        !file.type.startsWith('image\/') &&\n        !['.jpg','.jpeg','.png','.webp','.heic','.heif'].some(function(ext){ return file.name.toLowerCase().endsWith(ext); })\n    ){\n\n        alert(\n            'Vui l\u00f2ng ch\u1ecdn m\u1ed9t t\u1ec7p h\u00ecnh \u1ea3nh.'\n        );\n\n        return;\n\n    }\n\n\n    if(\n        file.size >\n        MAX_FILE_SIZE\n    ){\n\n        alert(\n            '\u1ea2nh t\u1ed1i \u0111a 20MB.'\n        );\n\n        return;\n\n    }\n\n\n    const reader =\n        new FileReader();\n\n\n    reader.onload =\n        function(e){\n\n            const img =\n                new Image();\n\n\n            img.onload =\n                function(){\n\n                    image=img;\n\n                    initialize();\n\n                };\n\n\n            img.onerror =\n                function(){\n\n                    alert(\n                        'Kh\u00f4ng th\u1ec3 \u0111\u1ecdc \u1ea3nh.'\n                    );\n\n                };\n\n\n            img.src =\n                e.target.result;\n\n        };\n\n\n    reader.readAsDataURL(file);\n\n}\n\n\n\/* ============================================================\n   INITIALIZE\n============================================================ *\/\n\nfunction initialize(){\n\n    let width =\n        image.naturalWidth;\n\n\n    let height =\n        image.naturalHeight;\n\n\n    let scale=1;\n\n\n    \/* Mobile safety: gi\u1eef t\u1ed5ng s\u1ed1 pixel \u1edf m\u1ee9c m\u00e0 Safari\/Android th\u01b0\u1eddng x\u1eed l\u00fd \u1ed5n \u0111\u1ecbnh. *\/\n    if(IS_MOBILE_DEVICE){\n        const mobilePixelBudget = 1400*1400;\n        const currentPixels = width*height;\n        if(currentPixels > mobilePixelBudget){\n            scale = Math.min(scale, Math.sqrt(mobilePixelBudget\/currentPixels));\n        }\n    }\n\n\n    if(\n        width>MAX_IMAGE_SIZE ||\n        height>MAX_IMAGE_SIZE\n    ){\n\n        scale =\n            Math.min(\n                MAX_IMAGE_SIZE\/width,\n                MAX_IMAGE_SIZE\/height\n            );\n\n    }\n\n\n    width =\n        Math.round(\n            width*scale\n        );\n\n\n    height =\n        Math.round(\n            height*scale\n        );\n\n    \/*\n     * MOBILE: fit the actual processing canvas to the available width.\n     * \u0110\u00e2y l\u00e0 \u0111i\u1ec3m quan tr\u1ecdng: thay v\u00ec gi\u1eef canvas 1400px r\u1ed3i c\u1ed1 thu nh\u1ecf\n     * b\u1eb1ng CSS (d\u1ec5 l\u1ed7i tr\u00ean Safari\/Chrome mobile), ta thu nh\u1ecf ch\u00ednh canvas\n     * n\u1ed9i b\u1ed9. T\u1ea5t c\u1ea3 c\u00e1c canvas x\u1eed l\u00fd d\u00f9ng c\u00f9ng k\u00edch th\u01b0\u1edbc n\u00ean t\u1ecda \u0111\u1ed9,\n     * mask, blur v\u00e0 AI v\u1eabn \u0111\u1ed3ng b\u1ed9.\n     *\/\n    if(IS_MOBILE_DEVICE || (window.matchMedia && window.matchMedia('(max-width:850px)').matches)){\n        const rootWidth = root.clientWidth || window.innerWidth || width;\n        const availableWidth = Math.max(240, rootWidth - 32);\n        if(width > availableWidth){\n            const fit = availableWidth \/ width;\n            width = Math.max(1, Math.round(width * fit));\n            height = Math.max(1, Math.round(height * fit));\n        }\n    }\n\n\n    [\n        originalCanvas,\n        blurMaskCanvas,\n        mosaicMaskCanvas,\n        aiBlurMaskCanvas,\n        aiMosaicMaskCanvas,\n        blurCanvas,\n        mosaicCanvas,\n        canvas\n    ].forEach(\n        function(c){\n\n            c.width=width;\n\n            c.height=height;\n\n        }\n    );\n\n\n    originalCtx.clearRect(\n        0,\n        0,\n        width,\n        height\n    );\n\n\n    originalCtx.drawImage(\n        image,\n        0,\n        0,\n        width,\n        height\n    );\n\n\n    clearMasks();\n\n\n    fullImageBlur=false;\n\n\n    zoom=1;\n\n\n    history=[];\n\n    historyIndex=-1;\n\n\n    sizeLabel.textContent =\n        width+\n        ' \u00d7 '+\n        height+\n        ' px';\n\n\n    upload.hidden=true;\n\n    editor.hidden=false;\n\n\n    saveHistory();\n\n    render();\n\n    \/* Mobile\/Safari: force one layout pass after the editor becomes visible. *\/\n    if(window.matchMedia && window.matchMedia('(max-width:850px)').matches){\n        setTimeout(function(){\n            render();\n        }, 60);\n    }\n\n\n    setStatus(\n        '\u1ea2nh \u0111\u00e3 s\u1eb5n s\u00e0ng.'\n    );\n\n}\n\n\n\/* ============================================================\n   CLEAR MASKS\n============================================================ *\/\n\nfunction clearMasks(){\n\n    const list=[\n\n        [\n            blurMaskCtx,\n            blurMaskCanvas\n        ],\n\n        [\n            mosaicMaskCtx,\n            mosaicMaskCanvas\n        ],\n\n        [\n            aiBlurMaskCtx,\n            aiBlurMaskCanvas\n        ],\n\n        [\n            aiMosaicMaskCtx,\n            aiMosaicMaskCanvas\n        ]\n\n    ];\n\n\n    list.forEach(\n        function(item){\n\n            item[0].clearRect(\n                0,\n                0,\n                item[1].width,\n                item[1].height\n            );\n\n        }\n    );\n\n}\n\n\n\/* ============================================================\n   CREATE BLUR\n============================================================ *\/\n\nfunction createBlur(){\n\n    const w=originalCanvas.width;\n    const h=originalCanvas.height;\n\n    if(!w || !h){\n        return;\n    }\n\n    \/*\n     * Kh\u00f4ng ph\u1ee5 thu\u1ed9c CanvasRenderingContext2D.filter.\n     * Tr\u00ean m\u1ed9t s\u1ed1 Safari\/iOS, filter blur tr\u00ean canvas l\u1edbn c\u00f3 th\u1ec3\n     * r\u1ea5t ch\u1eadm ho\u1eb7c cho k\u1ebft qu\u1ea3 kh\u00f4ng \u1ed5n \u0111\u1ecbnh. Downscale -> upscale\n     * t\u1ea1o hi\u1ec7u \u1ee9ng blur \u1ed5n \u0111\u1ecbnh h\u01a1n v\u00e0 nh\u1eb9 h\u01a1n.\n     *\/\n    const ratio=clamp(\n        1\/(2.5+blurAmount*.28),\n        .035,\n        .22\n    );\n\n    const sw=Math.max(1,Math.round(w*ratio));\n    const sh=Math.max(1,Math.round(h*ratio));\n\n    if(blurSmallCanvas.width!==sw || blurSmallCanvas.height!==sh){\n        blurSmallCanvas.width=sw;\n        blurSmallCanvas.height=sh;\n    }\n\n    blurSmallCtx.setTransform(1,0,0,1,0,0);\n    blurSmallCtx.globalCompositeOperation='source-over';\n    blurSmallCtx.imageSmoothingEnabled=true;\n    blurSmallCtx.clearRect(0,0,sw,sh);\n    blurSmallCtx.drawImage(originalCanvas,0,0,sw,sh);\n\n    blurCtx.setTransform(1,0,0,1,0,0);\n    blurCtx.globalCompositeOperation='source-over';\n    blurCtx.imageSmoothingEnabled=true;\n    blurCtx.clearRect(0,0,w,h);\n    blurCtx.drawImage(blurSmallCanvas,0,0,sw,sh,0,0,w,h);\n\n}\n\n\n\n\/* ============================================================\n   CREATE MOSAIC\n============================================================ *\/\n\nfunction createMosaic(){\n\n    const w =\n        originalCanvas.width;\n\n    const h =\n        originalCanvas.height;\n\n\n    mosaicCtx.clearRect(\n        0,\n        0,\n        w,\n        h\n    );\n\n\n    const block =\n        clamp(\n            Math.round(\n                8+\n                blurAmount*.75\n            ),\n            8,\n            45\n        );\n\n\n    const small = window.__boa32MosaicSmall || (window.__boa32MosaicSmall=document.createElement('canvas'));\n\n    small.width =\n        Math.max(\n            1,\n            Math.ceil(w\/block)\n        );\n\n\n    small.height =\n        Math.max(\n            1,\n            Math.ceil(h\/block)\n        );\n\n\n    const sctx =\n        small.getContext('2d');\n\n\n    sctx.drawImage(\n        originalCanvas,\n        0,\n        0,\n        small.width,\n        small.height\n    );\n\n\n    mosaicCtx.imageSmoothingEnabled=false;\n\n\n    mosaicCtx.drawImage(\n        small,\n        0,\n        0,\n        small.width,\n        small.height,\n        0,\n        0,\n        w,\n        h\n    );\n\n}\n\n\n\/* ============================================================\n   APPLY MASK\n============================================================ *\/\n\nfunction applyMaskedImage(\n    targetCtx,\n    effectCanvas,\n    maskCanvas\n){\n\n    const temp =\n        document.createElement(\n            'canvas'\n        );\n\n\n    temp.width =\n        canvas.width;\n\n\n    temp.height =\n        canvas.height;\n\n\n    const tctx =\n        temp.getContext('2d');\n\n\n    tctx.drawImage(\n        effectCanvas,\n        0,\n        0\n    );\n\n\n    tctx.globalCompositeOperation =\n        'destination-in';\n\n\n    tctx.drawImage(\n        maskCanvas,\n        0,\n        0\n    );\n\n\n    tctx.globalCompositeOperation =\n        'source-over';\n\n\n    targetCtx.drawImage(\n        temp,\n        0,\n        0\n    );\n\n}\n\n\n\/* ============================================================\n   RENDER\n============================================================ *\/\n\nfunction render(){\n\n    if(renderFramePending){\n        renderRequestedAgain=true;\n        return;\n    }\n\n    renderFramePending=true;\n\n    const paint=function(){\n\n        renderFramePending=false;\n        renderRequestedAgain=false;\n\n\n    if(\n        !originalCanvas.width\n    ){\n\n        return;\n\n    }\n\n\n    \/*\n     * MOBILE DISPLAY\n     * Tr\u00ean mobile, canvas n\u1ed9i b\u1ed9 \u0111\u00e3 \u0111\u01b0\u1ee3c thu nh\u1ecf ngay khi initialize()\n     * \u0111\u1ec3 v\u1eeba v\u00f9ng hi\u1ec3n th\u1ecb. V\u00ec v\u1eady \u1edf zoom 100% kh\u00f4ng d\u00f9ng CSS auto sizing\n     * n\u1eefa; ta \u0111\u1eb7t k\u00edch th\u01b0\u1edbc CSS \u0111\u00fang b\u1eb1ng k\u00edch th\u01b0\u1edbc n\u1ed9i t\u1ea1i c\u1ee7a canvas.\n     * \u0110i\u1ec1u n\u00e0y tr\u00e1nh l\u1ed7i Safari\/Chrome mobile: c\u00f3 v\u00f9ng canvas nh\u01b0ng bitmap\n     * kh\u00f4ng \u0111\u01b0\u1ee3c paint khi CSS width\/height v\u00e0 canvas.width\/canvas.height\n     * kh\u00f4ng \u0111\u1ed3ng b\u1ed9.\n     *\/\n    const isMobileViewport =\n        window.matchMedia &&\n        window.matchMedia('(max-width:850px)').matches;\n\n    let displayWidth = Math.round(canvas.width * zoom);\n    let displayHeight = Math.round(canvas.height * zoom);\n\n    if(isMobileViewport && zoom <= 1){\n        displayWidth = canvas.width;\n        displayHeight = canvas.height;\n    }\n\n    canvas.style.setProperty('width', displayWidth + 'px', 'important');\n    canvas.style.setProperty('height', displayHeight + 'px', 'important');\n    canvas.style.setProperty('max-width', 'none', 'important');\n    canvas.style.setProperty('max-height', 'none', 'important');\n\n    zoomLabel.textContent =\n        Math.round(\n            zoom*100\n        )+\n        '%';\n\n\n    ctx.clearRect(\n        0,\n        0,\n        canvas.width,\n        canvas.height\n    );\n\n\n    \/*\n     * Lu\u00f4n b\u1eaft \u0111\u1ea7u t\u1eeb \u1ea3nh g\u1ed1c.\n     *\/\n\n    ctx.drawImage(\n        originalCanvas,\n        0,\n        0\n    );\n\n\n    createBlur();\n\n    createMosaic();\n\n\n    \/*\n     * TO\u00c0N \u1ea2NH\n     *\n     * \u0110\u00e2y l\u00e0 l\u1edbp ri\u00eang.\n     *\/\n\n    if(\n        fullImageBlur\n    ){\n\n        ctx.drawImage(\n            blurCanvas,\n            0,\n            0\n        );\n\n    }\n\n\n    \/*\n     * V\u00f9ng blur th\u1ee7 c\u00f4ng.\n     *\/\n\n    applyMaskedImage(\n        ctx,\n        blurCanvas,\n        blurMaskCanvas\n    );\n\n\n    \/*\n     * V\u00f9ng AI blur.\n     *\/\n\n    applyMaskedImage(\n        ctx,\n        blurCanvas,\n        aiBlurMaskCanvas\n    );\n\n\n    \/*\n     * V\u00f9ng mosaic th\u1ee7 c\u00f4ng.\n     *\/\n\n    applyMaskedImage(\n        ctx,\n        mosaicCanvas,\n        mosaicMaskCanvas\n    );\n\n\n    \/*\n     * V\u00f9ng AI mosaic.\n     *\/\n\n    applyMaskedImage(\n        ctx,\n        mosaicCanvas,\n        aiMosaicMaskCanvas\n    );\n\n\n        if(renderRequestedAgain){\n            render();\n        }\n\n    };\n\n    if(typeof requestAnimationFrame==='function'){\n        requestAnimationFrame(paint);\n    }else{\n        setTimeout(paint,0);\n    }\n\n}\n\n\n\n\/* ============================================================\n   POINTER POSITION\n============================================================ *\/\n\nfunction getPoint(event){\n\n    const rect =\n        canvas.getBoundingClientRect();\n\n\n    return {\n\n        x:\n            (event.clientX-rect.left)*\n            canvas.width\/\n            rect.width,\n\n        y:\n            (event.clientY-rect.top)*\n            canvas.height\/\n            rect.height\n\n    };\n\n}\n\n\n\/* ============================================================\n   BRUSH\n============================================================ *\/\n\nfunction drawBrush(\n    point,\n    erase\n){\n\n    const radius =\n        brushSize\/2;\n\n\n    blurMaskCtx.save();\n\n\n    blurMaskCtx.globalCompositeOperation =\n        erase\n            ? 'destination-out'\n            : 'source-over';\n\n\n    blurMaskCtx.fillStyle =\n        '#ffffff';\n\n\n    blurMaskCtx.beginPath();\n\n\n    blurMaskCtx.arc(\n        point.x,\n        point.y,\n        radius,\n        0,\n        Math.PI*2\n    );\n\n\n    blurMaskCtx.fill();\n\n\n    blurMaskCtx.restore();\n\n}\n\n\n\/* ============================================================\n   BRUSH LINE\n============================================================ *\/\n\nfunction drawLine(\n    a,\n    b,\n    erase\n){\n\n    const distance =\n        Math.hypot(\n            b.x-a.x,\n            b.y-a.y\n        );\n\n\n    const steps =\n        Math.max(\n            1,\n            Math.ceil(\n                distance\/\n                Math.max(\n                    2,\n                    brushSize*.15\n                )\n            )\n        );\n\n\n    for(\n        let i=1;\n        i<=steps;\n        i++\n    ){\n\n        const t =\n            i\/steps;\n\n\n        drawBrush(\n            {\n\n                x:\n                    a.x+\n                    (b.x-a.x)*t,\n\n                y:\n                    a.y+\n                    (b.y-a.y)*t\n\n            },\n            erase\n        );\n\n    }\n\n}\n\n\n\/* ============================================================\n   RECTANGLE\n============================================================ *\/\n\nfunction drawRectangle(\n    a,\n    b,\n    targetCtx,\n    erase\n){\n\n    const x =\n        Math.min(\n            a.x,\n            b.x\n        );\n\n\n    const y =\n        Math.min(\n            a.y,\n            b.y\n        );\n\n\n    const w =\n        Math.abs(\n            b.x-a.x\n        );\n\n\n    const h =\n        Math.abs(\n            b.y-a.y\n        );\n\n\n    targetCtx.save();\n\n\n    targetCtx.globalCompositeOperation =\n        erase\n            ? 'destination-out'\n            : 'source-over';\n\n\n    targetCtx.fillStyle =\n        '#ffffff';\n\n\n    targetCtx.fillRect(\n        x,\n        y,\n        w,\n        h\n    );\n\n\n    targetCtx.restore();\n\n}\n\n\n\/* ============================================================\n   CIRCLE\n============================================================ *\/\n\nfunction drawCircle(\n    a,\n    b,\n    targetCtx,\n    erase\n){\n\n    const radius =\n        Math.hypot(\n            b.x-a.x,\n            b.y-a.y\n        );\n\n\n    targetCtx.save();\n\n\n    targetCtx.globalCompositeOperation =\n        erase\n            ? 'destination-out'\n            : 'source-over';\n\n\n    targetCtx.fillStyle =\n        '#ffffff';\n\n\n    targetCtx.beginPath();\n\n\n    targetCtx.arc(\n        a.x,\n        a.y,\n        radius,\n        0,\n        Math.PI*2\n    );\n\n\n    targetCtx.fill();\n\n\n    targetCtx.restore();\n\n}\n\n\n\/* ============================================================\n   POINTER DOWN\n============================================================ *\/\n\ncanvas.addEventListener(\n    'pointerdown',\n    function(event){\n\n        if(!image){\n            return;\n        }\n\n\n        if(\n            mode==='full'\n        ){\n\n            return;\n\n        }\n\n\n        drawing=true;\n\n\n        event.preventDefault();\n\n        try{\n            canvas.setPointerCapture(event.pointerId);\n        }catch(error){}\n\n\n        startPoint =\n            getPoint(event);\n\n\n        lastPoint =\n            startPoint;\n\n\n        if(\n            mode==='brush'\n        ){\n\n            drawBrush(\n                startPoint,\n                false\n            );\n\n            render();\n\n        }\n\n\n        if(\n            mode==='erase'\n        ){\n\n            drawBrush(\n                startPoint,\n                true\n            );\n\n            render();\n\n        }\n\n    }\n);\n\n\n\/* ============================================================\n   POINTER MOVE\n============================================================ *\/\n\ncanvas.addEventListener(\n    'pointermove',\n    function(event){\n\n        if(!drawing){\n            return;\n        }\n\n        event.preventDefault();\n\n\n        const point =\n            getPoint(event);\n\n\n        if(\n            mode==='brush'\n        ){\n\n            drawLine(\n                lastPoint,\n                point,\n                false\n            );\n\n\n            lastPoint =\n                point;\n\n\n            render();\n\n        }\n\n\n        if(\n            mode==='erase'\n        ){\n\n            drawLine(\n                lastPoint,\n                point,\n                true\n            );\n\n\n            lastPoint =\n                point;\n\n\n            render();\n\n        }\n\n    }\n);\n\n\n\/* ============================================================\n   POINTER UP\n============================================================ *\/\n\ncanvas.addEventListener(\n    'pointerup',\n    function(event){\n\n        if(!drawing){\n            return;\n        }\n\n        event.preventDefault();\n\n\n        const point =\n            getPoint(event);\n\n\n        if(\n            mode==='rectangle'\n        ){\n\n            drawRectangle(\n                startPoint,\n                point,\n                blurMaskCtx,\n                false\n            );\n\n        }\n\n\n        if(\n            mode==='circle'\n        ){\n\n            drawCircle(\n                startPoint,\n                point,\n                blurMaskCtx,\n                false\n            );\n\n        }\n\n\n        if(\n            mode==='mosaic'\n        ){\n\n            drawRectangle(\n                startPoint,\n                point,\n                mosaicMaskCtx,\n                false\n            );\n\n        }\n\n\n        drawing=false;\n\n\n        startPoint=null;\n\n        lastPoint=null;\n\n\n        render();\n\n        saveHistory();\n\n    }\n);\n\n\n\/* ============================================================\n   POINTER CANCEL\n============================================================ *\/\n\ncanvas.addEventListener(\n    'pointercancel',\n    function(){\n\n        drawing=false;\n\n        startPoint=null;\n\n        lastPoint=null;\n\n    }\n);\n\n\n\/* ============================================================\n   TOOL SELECTION\n============================================================ *\/\n\ntoolButtons.forEach(\n    function(button){\n\n        button.addEventListener(\n            'click',\n            function(){\n\n                const newMode =\n                    button.dataset.mode;\n\n\n                \/*\n                 * FIX L\u1ed6I TO\u00c0N \u1ea2NH.\n                 *\n                 * Khi ch\u1ecdn b\u1ea5t k\u1ef3 c\u00f4ng c\u1ee5 n\u00e0o kh\u00e1c\n                 * th\u00ec fullImageBlur b\u1ecb t\u1eaft.\n                 *\/\n\n                if(\n                    newMode!=='full'\n                ){\n\n                    fullImageBlur=false;\n\n                }\n\n\n                mode=\n                    newMode;\n\n\n                toolButtons.forEach(\n                    function(btn){\n\n                        btn.classList.remove(\n                            'active'\n                        );\n\n                    }\n                );\n\n\n                button.classList.add(\n                    'active'\n                );\n\n\n                updateToolUI();\n\n\n                render();\n\n            }\n        );\n\n    }\n);\n\n\n\/* ============================================================\n   TOOL UI\n============================================================ *\/\n\nfunction updateToolUI(){\n\n    brushControl.style.display='none';\n\n\n    if(\n        mode==='brush'\n    ){\n\n        help.textContent =\n            '\ud83d\udd8c\ufe0f T\u00f4 l\u00ean v\u00f9ng mu\u1ed1n l\u00e0m m\u1edd.';\n\n        brushControl.style.display='';\n\n    }\n\n\n    if(\n        mode==='erase'\n    ){\n\n        help.textContent =\n            '\ud83e\uddfd T\u00f4 l\u00ean v\u00f9ng blur \u0111\u1ec3 x\u00f3a.';\n\n        brushControl.style.display='';\n\n    }\n\n\n    if(\n        mode==='rectangle'\n    ){\n\n        help.textContent =\n            '\u25ad K\u00e9o chu\u1ed9t \u0111\u1ec3 t\u1ea1o v\u00f9ng ch\u1eef nh\u1eadt c\u1ea7n l\u00e0m m\u1edd.';\n\n    }\n\n\n    if(\n        mode==='circle'\n    ){\n\n        help.textContent =\n            '\u2b55 K\u00e9o chu\u1ed9t \u0111\u1ec3 t\u1ea1o v\u00f9ng h\u00ecnh tr\u00f2n c\u1ea7n l\u00e0m m\u1edd.';\n\n    }\n\n\n    if(\n        mode==='mosaic'\n    ){\n\n        help.textContent =\n            '\ud83d\udd32 K\u00e9o chu\u1ed9t \u0111\u1ec3 t\u1ea1o v\u00f9ng Mosaic.';\n\n    }\n\n\n    if(\n        mode==='full'\n    ){\n\n        help.textContent =\n            '\ud83c\udf2b\ufe0f To\u00e0n b\u1ed9 \u1ea3nh \u0111ang \u0111\u01b0\u1ee3c l\u00e0m m\u1edd.';\n\n\n        fullImageBlur=true;\n\n\n        setStatus(\n            '\u0110\u00e3 b\u1eadt l\u00e0m m\u1edd to\u00e0n \u1ea3nh.'\n        );\n\n    }\n\n}\n\n\n\/* ============================================================\n   BLUR\n============================================================ *\/\n\nblurRange.addEventListener(\n    'input',\n    function(){\n\n        blurAmount =\n            parseInt(\n                this.value,\n                10\n            );\n\n\n        blurLabel.textContent =\n            blurAmount;\n\n\n        render();\n\n    }\n);\n\n\n\/* ============================================================\n   BRUSH SIZE\n============================================================ *\/\n\nbrushRange.addEventListener(\n    'input',\n    function(){\n\n        brushSize =\n            parseInt(\n                this.value,\n                10\n            );\n\n\n        brushLabel.textContent =\n            brushSize;\n\n    }\n);\n\n\n\/* ============================================================\n   CONFIDENCE\n============================================================ *\/\n\nconfidence.addEventListener(\n    'input',\n    function(){\n\n        confidenceLabel.textContent =\n            this.value+\n            '%';\n\n    }\n);\n\n\n\/* ============================================================\n   ZOOM\n============================================================ *\/\n\nzoomIn.addEventListener(\n    'click',\n    function(){\n\n        zoom =\n            clamp(\n                zoom+.1,\n                .25,\n                3\n            );\n\n\n        render();\n\n    }\n);\n\n\nzoomOut.addEventListener(\n    'click',\n    function(){\n\n        zoom =\n            clamp(\n                zoom-.1,\n                .25,\n                3\n            );\n\n\n        render();\n\n    }\n);\n\n\nzoomReset.addEventListener(\n    'click',\n    function(){\n\n        zoom=1;\n\n        render();\n\n    }\n);\n\n\n\/* ============================================================\n   HISTORY SNAPSHOT\n============================================================ *\/\n\nfunction getImageDataSafe(\n    context,\n    canvasElement\n){\n\n    return context.getImageData(\n        0,\n        0,\n        canvasElement.width,\n        canvasElement.height\n    );\n\n}\n\n\nfunction createSnapshot(){\n\n    return {\n\n        blur:\n            getImageDataSafe(\n                blurMaskCtx,\n                blurMaskCanvas\n            ),\n\n        mosaic:\n            getImageDataSafe(\n                mosaicMaskCtx,\n                mosaicMaskCanvas\n            ),\n\n        aiBlur:\n            getImageDataSafe(\n                aiBlurMaskCtx,\n                aiBlurMaskCanvas\n            ),\n\n        aiMosaic:\n            getImageDataSafe(\n                aiMosaicMaskCtx,\n                aiMosaicMaskCanvas\n            ),\n\n        full:\n            fullImageBlur,\n\n        blurAmount:\n            blurAmount\n\n    };\n\n}\n\n\nfunction restoreSnapshot(\n    snapshot\n){\n\n    blurMaskCtx.putImageData(\n        snapshot.blur,\n        0,\n        0\n    );\n\n\n    mosaicMaskCtx.putImageData(\n        snapshot.mosaic,\n        0,\n        0\n    );\n\n\n    aiBlurMaskCtx.putImageData(\n        snapshot.aiBlur,\n        0,\n        0\n    );\n\n\n    aiMosaicMaskCtx.putImageData(\n        snapshot.aiMosaic,\n        0,\n        0\n    );\n\n\n    fullImageBlur =\n        snapshot.full;\n\n\n    blurAmount =\n        snapshot.blurAmount;\n\n\n    blurRange.value =\n        blurAmount;\n\n\n    blurLabel.textContent =\n        blurAmount;\n\n\n    render();\n\n}\n\n\nfunction saveHistory(){\n\n    if(\n        !originalCanvas.width\n    ){\n\n        return;\n\n    }\n\n\n    if(\n        historyIndex<\n        history.length-1\n    ){\n\n        history =\n            history.slice(\n                0,\n                historyIndex+1\n            );\n\n    }\n\n\n    history.push(\n        createSnapshot()\n    );\n\n\n    if(\n        history.length>20\n    ){\n\n        history.shift();\n\n    }\n\n\n    historyIndex =\n        history.length-1;\n\n}\n\n\n\/* ============================================================\n   UNDO\n============================================================ *\/\n\nundoButton.addEventListener(\n    'click',\n    function(){\n\n        if(\n            historyIndex>0\n        ){\n\n            historyIndex--;\n\n\n            restoreSnapshot(\n                history[\n                    historyIndex\n                ]\n            );\n\n        }\n\n    }\n);\n\n\n\/* ============================================================\n   REDO\n============================================================ *\/\n\nredoButton.addEventListener(\n    'click',\n    function(){\n\n        if(\n            historyIndex<\n            history.length-1\n        ){\n\n            historyIndex++;\n\n\n            restoreSnapshot(\n                history[\n                    historyIndex\n                ]\n            );\n\n        }\n\n    }\n);\n\n\n\/* ============================================================\n   RESET\n============================================================ *\/\n\nresetButton.addEventListener(\n    'click',\n    function(){\n\n        clearMasks();\n\n\n        fullImageBlur=false;\n\n\n        mode='brush';\n\n\n        toolButtons.forEach(\n            function(btn){\n\n                btn.classList.remove(\n                    'active'\n                );\n\n            }\n        );\n\n\n        root.querySelector(\n            '[data-mode=\"brush\"]'\n        ).classList.add(\n            'active'\n        );\n\n\n        aiResults.innerHTML='';\n\n\n        setAIStatus(\n            'Ch\u01b0a ch\u1ea1y'\n        );\n\n\n        history=[];\n\n        historyIndex=-1;\n\n\n        updateToolUI();\n\n\n        saveHistory();\n\n\n        render();\n\n\n        setStatus(\n            '\u0110\u00e3 \u0111\u1eb7t l\u1ea1i \u1ea3nh.'\n        );\n\n    }\n);\n\n\n\/* ============================================================\n   WAIT FOR AI LIBRARIES\n============================================================ *\/\n\nconst scriptPromises = {};\n\nfunction loadExternalScript(src){\n\n    if(scriptPromises[src]){\n        return scriptPromises[src];\n    }\n\n    scriptPromises[src] = new Promise(function(resolve,reject){\n\n        if(Array.from(document.scripts).some(function(s){ return s.src === src; })){\n            var started=Date.now();\n            var poll=setInterval(function(){\n                if(src.indexOf('tf.min.js')!==-1 && typeof window.tf!=='undefined'){ clearInterval(poll); resolve(); }\n                else if(src.indexOf('blazeface')!==-1 && typeof window.blazeface!=='undefined'){ clearInterval(poll); resolve(); }\n                else if(src.indexOf('coco-ssd')!==-1 && typeof window.cocoSsd!=='undefined'){ clearInterval(poll); resolve(); }\n                else if(Date.now()-started>30000){ clearInterval(poll); reject(new Error('Timeout t\u1ea3i th\u01b0 vi\u1ec7n AI')); }\n            },100);\n            return;\n        }\n\n        var script=document.createElement('script');\n        script.src=src;\n        script.async=true;\n        script.onload=function(){ resolve(); };\n        script.onerror=function(){ reject(new Error('Kh\u00f4ng t\u1ea3i \u0111\u01b0\u1ee3c th\u01b0 vi\u1ec7n AI: '+src)); };\n        document.head.appendChild(script);\n    });\n\n    return scriptPromises[src];\n}\n\nasync function waitForLibraries(type){\n\n    await loadExternalScript('https:\/\/cdn.jsdelivr.net\/npm\/@tensorflow\/tfjs@4.22.0\/dist\/tf.min.js');\n\n    if(typeof window.tf==='undefined'){\n        throw new Error('TensorFlow.js ch\u01b0a s\u1eb5n s\u00e0ng');\n    }\n\n    await tf.ready();\n\n    if(type==='face'){\n        await loadExternalScript('https:\/\/cdn.jsdelivr.net\/npm\/@tensorflow-models\/blazeface@0.0.7\/dist\/blazeface.min.js');\n        if(typeof window.blazeface==='undefined'){\n            throw new Error('BlazeFace ch\u01b0a s\u1eb5n s\u00e0ng');\n        }\n    }else{\n        await loadExternalScript('https:\/\/cdn.jsdelivr.net\/npm\/@tensorflow-models\/coco-ssd@2.2.3\/dist\/coco-ssd.min.js');\n        if(typeof window.cocoSsd==='undefined'){\n            throw new Error('COCO-SSD ch\u01b0a s\u1eb5n s\u00e0ng');\n        }\n    }\n}\n\n\n\n\/* ============================================================\n   LOAD COCO\n============================================================ *\/\n\nasync function loadCoco(){\n\n    if(cocoModel){\n\n        return cocoModel;\n\n    }\n\n\n    await waitForLibraries('coco');\n\n\n    await tf.ready();\n\n\n    cocoModel =\n        await cocoSsd.load(\n            {\n                base:\n                    'lite_mobilenet_v2'\n            }\n        );\n\n\n    return cocoModel;\n\n}\n\n\n\/* ============================================================\n   LOAD FACE\n============================================================ *\/\n\nasync function loadFace(){\n\n    if(faceModel){\n\n        return faceModel;\n\n    }\n\n\n    await waitForLibraries('face');\n\n\n    await tf.ready();\n\n\n    faceModel =\n        await blazeface.load();\n\n\n    return faceModel;\n\n}\n\n\n\/* ============================================================\n   GET SELECTED OBJECTS\n============================================================ *\/\n\nfunction getSelectedObjects(){\n\n    return Array.from(\n        objectList.querySelectorAll(\n            '[data-object-checkbox]:checked'\n        )\n    ).map(\n        function(cb){\n\n            return cb.value;\n\n        }\n    );\n\n}\n\n\n\/* ============================================================\n   ADD AI BOX\n============================================================ *\/\n\nfunction addAIMaskBox(\n    x,\n    y,\n    width,\n    height,\n    targetCtx\n){\n\n    \/*\n     * Th\u00eam m\u1ed9t ch\u00fat padding \u0111\u1ec3 kh\u00f4ng b\u1ecb h\u1edf vi\u1ec1n\n     * quanh khu\u00f4n m\u1eb7t\/v\u1eadt th\u1ec3.\n     *\/\n\n    const pad =\n        Math.max(\n            3,\n            Math.min(\n                width,\n                height\n            )*.06\n        );\n\n\n    const px =\n        Math.max(\n            0,\n            x-pad\n        );\n\n\n    const py =\n        Math.max(\n            0,\n            y-pad\n        );\n\n\n    const right =\n        Math.min(\n            canvas.width,\n            x+width+pad\n        );\n\n\n    const bottom =\n        Math.min(\n            canvas.height,\n            y+height+pad\n        );\n\n\n    targetCtx.fillStyle =\n        '#ffffff';\n\n\n    targetCtx.fillRect(\n        px,\n        py,\n        right-px,\n        bottom-py\n    );\n\n}\n\n\n\/* ============================================================\n   AI BUTTON\n============================================================ *\/\n\naiButton.addEventListener(\n    'click',\n    async function(){\n\n        if(aiRunning){\n            return;\n        }\n\n        if(!image){\n            return;\n        }\n\n\n        const selectedObjects =\n            getSelectedObjects();\n\n\n        const detectFace =\n            faceCheckbox.checked;\n\n\n        if(\n            !detectFace &&\n            selectedObjects.length===0\n        ){\n\n            alert(\n                'H\u00e3y ch\u1ecdn khu\u00f4n m\u1eb7t ho\u1eb7c \u00edt nh\u1ea5t m\u1ed9t v\u1eadt th\u1ec3.'\n            );\n\n            return;\n\n        }\n\n\n        aiRunning=true;\n\n        aiButton.disabled=true;\n\n\n        aiLoading.hidden=false;\n\n\n        aiResults.innerHTML='';\n\n\n        try{\n\n            await tf.ready();\n\n            try{\n                if(tf.getBackend()!=='webgl'){\n                    await tf.setBackend('webgl');\n                    await tf.ready();\n                }\n            }catch(webglError){\n                try{\n                    await tf.setBackend('cpu');\n                    await tf.ready();\n                }catch(cpuError){\n                    console.warn('BOA32: kh\u00f4ng chuy\u1ec3n \u0111\u01b0\u1ee3c backend TensorFlow.',cpuError);\n                }\n            }\n\n            const selectedEffect =\n                aiEffect.value;\n\n\n            const threshold =\n                parseInt(\n                    confidence.value,\n                    10\n                )\/100;\n\n\n            let totalFound=0;\n\n\n            \/*\n             * ==================================================\n             * FACE\n             * ==================================================\n             *\/\n\n            if(\n                detectFace\n            ){\n\n                aiLoadingTitle.textContent =\n                    '\u0110ang nh\u1eadn di\u1ec7n khu\u00f4n m\u1eb7t...';\n\n\n                aiLoadingText.textContent =\n                    'AI \u0111ang t\u00ecm c\u00e1c khu\u00f4n m\u1eb7t trong \u1ea3nh.';\n\n\n                setAIStatus(\n                    '\u0110ang t\u00ecm m\u1eb7t'\n                );\n\n\n                const model =\n                    await loadFace();\n\n\n                const predictions =\n                    await model.estimateFaces(\n                        originalCanvas,\n                        false\n                    );\n\n\n                predictions.forEach(\n                    function(face){\n\n                        \/*\n                         * BlazeFace c\u00f3 topLeft \/ bottomRight.\n                         *\/\n\n                        let x=0;\n\n                        let y=0;\n\n                        let w=0;\n\n                        let h=0;\n\n\n                        if(\n                            face.topLeft &&\n                            face.bottomRight\n                        ){\n\n                            x =\n                                face.topLeft[0];\n\n                            y =\n                                face.topLeft[1];\n\n\n                            w =\n                                face.bottomRight[0]-\n                                face.topLeft[0];\n\n\n                            h =\n                                face.bottomRight[1]-\n                                face.topLeft[1];\n\n                        }\n\n\n                        \/*\n                         * M\u1ed9t s\u1ed1 phi\u00ean b\u1ea3n tr\u1ea3 v\u1ec1\n                         * probability.\n                         *\/\n\n                        let score=1;\n\n\n                        if(\n                            face.probability\n                        ){\n\n                            if(\n                                Array.isArray(\n                                    face.probability\n                                )\n                            ){\n\n                                score =\n                                    face.probability[0] || 1;\n\n                            }\n\n                        }\n\n\n                        if(\n                            score>=threshold\n                        ){\n\n                            if(\n                                selectedEffect==='blur'\n                            ){\n\n                                addAIMaskBox(\n                                    x,\n                                    y,\n                                    w,\n                                    h,\n                                    aiBlurMaskCtx\n                                );\n\n                            }\n                            else{\n\n                                addAIMaskBox(\n                                    x,\n                                    y,\n                                    w,\n                                    h,\n                                    aiMosaicMaskCtx\n                                );\n\n                            }\n\n\n                            totalFound++;\n\n\n                            addResultTag(\n                                '\ud83d\udc64 Khu\u00f4n m\u1eb7t',\n                                Math.round(\n                                    score*100\n                                )\n                            );\n\n                        }\n\n                    }\n                );\n\n            }\n\n\n            \/*\n             * ==================================================\n             * OBJECTS\n             * ==================================================\n             *\/\n\n            if(\n                selectedObjects.length\n            ){\n\n                aiLoadingTitle.textContent =\n                    '\u0110ang nh\u1eadn di\u1ec7n v\u1eadt th\u1ec3...';\n\n\n                aiLoadingText.textContent =\n                    '\u0110ang ph\u00e2n t\u00edch c\u00e1c \u0111\u1ed1i t\u01b0\u1ee3ng \u0111\u00e3 ch\u1ecdn.';\n\n\n                setAIStatus(\n                    '\u0110ang t\u00ecm v\u1eadt th\u1ec3'\n                );\n\n\n                const model =\n                    await loadCoco();\n\n\n                const predictions =\n                    await model.detect(\n                        originalCanvas,\n                        40,\n                        threshold\n                    );\n\n\n                predictions.forEach(\n                    function(item){\n\n                        if(\n                            !selectedObjects.includes(\n                                item.class\n                            )\n                        ){\n\n                            return;\n\n                        }\n\n\n                        const bbox =\n                            item.bbox;\n\n\n                        if(\n                            !bbox ||\n                            bbox.length<4\n                        ){\n\n                            return;\n\n                        }\n\n\n                        if(\n                            selectedEffect==='blur'\n                        ){\n\n                            addAIMaskBox(\n                                bbox[0],\n                                bbox[1],\n                                bbox[2],\n                                bbox[3],\n                                aiBlurMaskCtx\n                            );\n\n                        }\n                        else{\n\n                            addAIMaskBox(\n                                bbox[0],\n                                bbox[1],\n                                bbox[2],\n                                bbox[3],\n                                aiMosaicMaskCtx\n                            );\n\n                        }\n\n\n                        totalFound++;\n\n\n                        const objectInfo =\n                            OBJECTS.find(\n                                function(obj){\n\n                                    return (\n                                        obj.id===\n                                        item.class\n                                    );\n\n                                }\n                            );\n\n\n                        addResultTag(\n                            (\n                                objectInfo\n                                    ? objectInfo.icon+' '+\n                                      objectInfo.name\n                                    : item.class\n                            ),\n                            Math.round(\n                                item.score*100\n                            )\n                        );\n\n                    }\n                );\n\n            }\n\n\n            \/*\n             * ==================================================\n             * RENDER\n             * ==================================================\n             *\/\n\n            render();\n\n\n            saveHistory();\n\n\n            if(\n                totalFound>0\n            ){\n\n                setAIStatus(\n                    '\u0110\u00e3 t\u00ecm '+totalFound\n                );\n\n\n                setStatus(\n                    'AI \u0111\u00e3 t\u00ecm th\u1ea5y '+\n                    totalFound+\n                    ' \u0111\u1ed1i t\u01b0\u1ee3ng\/khu\u00f4n m\u1eb7t.'\n                );\n\n            }\n            else{\n\n                setAIStatus(\n                    'Kh\u00f4ng t\u00ecm th\u1ea5y'\n                );\n\n\n                setStatus(\n                    'AI kh\u00f4ng t\u00ecm th\u1ea5y \u0111\u1ed1i t\u01b0\u1ee3ng ph\u00f9 h\u1ee3p.'\n                );\n\n            }\n\n        }\n\n        catch(error){\n\n            console.error(\n                'BOA32 AI ERROR:',\n                error\n            );\n\n\n            setAIStatus(\n                'L\u1ed7i AI'\n            );\n\n\n            setStatus(\n                'Kh\u00f4ng th\u1ec3 ch\u1ea1y AI.'\n            );\n\n\n            alert(\n                'Kh\u00f4ng th\u1ec3 t\u1ea3i ho\u1eb7c ch\u1ea1y AI. H\u00e3y ki\u1ec3m tra k\u1ebft n\u1ed1i Internet r\u1ed3i th\u1eed l\u1ea1i.'\n            );\n\n        }\n\n        finally{\n\n            aiRunning=false;\n\n            aiLoading.hidden=true;\n\n            aiButton.disabled=false;\n\n        }\n\n    }\n);\n\n\n\/* ============================================================\n   RESULT TAG\n============================================================ *\/\n\nfunction addResultTag(\n    name,\n    score\n){\n\n    const tag =\n        document.createElement(\n            'div'\n        );\n\n\n    tag.className =\n        'boa32-ai-tag';\n\n\n    tag.textContent =\n        name+\n        ' \u00b7 '+\n        score+\n        '%';\n\n\n    aiResults.appendChild(\n        tag\n    );\n\n}\n\n\n\/* ============================================================\n   DOWNLOAD\n============================================================ *\/\n\ndownloadButton.addEventListener(\n    'click',\n    function(){\n\n        if(!image){\n            return;\n        }\n\n\n        const output =\n            document.createElement(\n                'canvas'\n            );\n\n\n        output.width =\n            originalCanvas.width;\n\n\n        output.height =\n            originalCanvas.height;\n\n\n        const octx =\n            output.getContext('2d');\n\n\n        const format =\n            formatSelect.value;\n\n\n        \/*\n         * JPG ph\u1ea3i c\u00f3 n\u1ec1n tr\u1eafng.\n         *\/\n\n        if(\n            format==='image\/jpeg'\n        ){\n\n            octx.fillStyle =\n                '#ffffff';\n\n\n            octx.fillRect(\n                0,\n                0,\n                output.width,\n                output.height\n            );\n\n        }\n\n\n        \/*\n         * \u1ea2nh g\u1ed1c\n         *\/\n\n        octx.drawImage(\n            originalCanvas,\n            0,\n            0\n        );\n\n\n        createBlur();\n\n        createMosaic();\n\n\n        \/*\n         * Full image\n         *\/\n\n        if(\n            fullImageBlur\n        ){\n\n            octx.drawImage(\n                blurCanvas,\n                0,\n                0\n            );\n\n        }\n\n\n        \/*\n         * Manual blur\n         *\/\n\n        applyMaskedImage(\n            octx,\n            blurCanvas,\n            blurMaskCanvas\n        );\n\n\n        \/*\n         * AI blur\n         *\/\n\n        applyMaskedImage(\n            octx,\n            blurCanvas,\n            aiBlurMaskCanvas\n        );\n\n\n        \/*\n         * Manual mosaic\n         *\/\n\n        applyMaskedImage(\n            octx,\n            mosaicCanvas,\n            mosaicMaskCanvas\n        );\n\n\n        \/*\n         * AI mosaic\n         *\/\n\n        applyMaskedImage(\n            octx,\n            mosaicCanvas,\n            aiMosaicMaskCanvas\n        );\n\n\n        output.toBlob(\n            function(blob){\n\n                if(!blob){\n\n                    alert(\n                        'Kh\u00f4ng th\u1ec3 t\u1ea1o file \u1ea3nh.'\n                    );\n\n                    return;\n\n                }\n\n\n                let extension =\n                    'png';\n\n\n                if(\n                    format==='image\/jpeg'\n                ){\n\n                    extension='jpg';\n\n                }\n\n\n                if(\n                    format==='image\/webp'\n                ){\n\n                    extension='webp';\n\n                }\n\n\n                const url =\n                    URL.createObjectURL(\n                        blob\n                    );\n\n\n                const link =\n                    document.createElement(\n                        'a'\n                    );\n\n\n                link.href=url;\n\n\n                link.download =\n                    'anh-da-lam-mo.'+\n                    extension;\n\n\n                document.body.appendChild(\n                    link\n                );\n\n\n                link.click();\n\n\n                link.remove();\n\n\n                setTimeout(\n                    function(){\n\n                        URL.revokeObjectURL(\n                            url\n                        );\n\n                    },\n                    1000\n                );\n\n\n                setStatus(\n                    '\u0110\u00e3 t\u1ea3i \u1ea3nh xu\u1ed1ng.'\n                );\n\n            },\n            format,\n            .95\n        );\n\n    }\n);\n\n\n\/* ============================================================\n   NEW IMAGE\n============================================================ *\/\n\nnewButton.addEventListener(\n    'click',\n    function(){\n\n        image=null;\n\n\n        fileInput.value='';\n\n\n        editor.hidden=true;\n\n        upload.hidden=false;\n\n\n        clearMasks();\n\n\n        fullImageBlur=false;\n\n\n        history=[];\n\n        historyIndex=-1;\n\n\n        aiResults.innerHTML='';\n\n\n        setAIStatus(\n            'Ch\u01b0a ch\u1ea1y'\n        );\n\n\n        setStatus('');\n\n    }\n);\n\n\n\/* ============================================================\n   MOBILE RESIZE \/ ORIENTATION SAFETY\n============================================================ *\/\n\nfunction refreshCanvasViewport(){\n    if(!image || !canvas.width){ return; }\n    render();\n}\n\nwindow.addEventListener('orientationchange', function(){\n    setTimeout(refreshCanvasViewport,250);\n});\nwindow.addEventListener('resize', function(){\n    if(window.__boa32ResizeTimer){ clearTimeout(window.__boa32ResizeTimer); }\n    window.__boa32ResizeTimer=setTimeout(refreshCanvasViewport,120);\n});\n\n\n\/* ============================================================\n   INITIAL UI\n============================================================ *\/\n\nblurLabel.textContent =\n    blurAmount;\n\n\nbrushLabel.textContent =\n    brushSize;\n\n\nconfidenceLabel.textContent =\n    confidence.value+\n    '%';\n\n\nupdateToolUI();\n\n\n})();\n<\/script>\n\n\n<\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":676,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-607","page","type-page","status-publish","has-post-thumbnail"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI | App Mitunet<\/title>\n<meta name=\"description\" content=\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, \u1ee9ng d\u1ee5ng AI l\u00e0m m\u1edd \u1ea3nh hi\u1ec7u qu\u1ea3, c\u00e1ch l\u00e0m m\u1edd \u1ea3nh tr\u1ef1c tuy\u1ebfn tr\u00ean tr\u00ecnh duy\u1ec7t, gi\u1ea3i ph\u00e1p s\u1eeda h\u00ecnh \u1ea3nh...\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/\" \/>\n<meta property=\"og:locale\" content=\"vi_VN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI | App Mitunet\" \/>\n<meta property=\"og:description\" content=\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, \u1ee9ng d\u1ee5ng AI l\u00e0m m\u1edd \u1ea3nh hi\u1ec7u qu\u1ea3, c\u00e1ch l\u00e0m m\u1edd \u1ea3nh tr\u1ef1c tuy\u1ebfn tr\u00ean tr\u00ecnh duy\u1ec7t, gi\u1ea3i ph\u00e1p s\u1eeda h\u00ecnh \u1ea3nh...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/\" \/>\n<meta property=\"og:site_name\" content=\"App Mitunet\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-15T11:17:51+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/lam-mo-anh-online.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"800\" \/>\n\t<meta property=\"og:image:height\" content=\"600\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u01af\u1edbc t\u00ednh th\u1eddi gian \u0111\u1ecdc\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 ph\u00fat\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/\",\"url\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/\",\"name\":\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI | App Mitunet\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/lam-mo-anh-online.jpg\",\"datePublished\":\"2026-08-12T15:32:55+00:00\",\"dateModified\":\"2026-08-15T11:17:51+00:00\",\"description\":\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, \u1ee9ng d\u1ee5ng AI l\u00e0m m\u1edd \u1ea3nh hi\u1ec7u qu\u1ea3, c\u00e1ch l\u00e0m m\u1edd \u1ea3nh tr\u1ef1c tuy\u1ebfn tr\u00ean tr\u00ecnh duy\u1ec7t, gi\u1ea3i ph\u00e1p s\u1eeda h\u00ecnh \u1ea3nh...\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/#breadcrumb\"},\"inLanguage\":\"vi\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"vi\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/#primaryimage\",\"url\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/lam-mo-anh-online.jpg\",\"contentUrl\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/lam-mo-anh-online.jpg\",\"width\":800,\"height\":600,\"caption\":\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, l\u00e0m m\u1edd h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/lam-mo-anh\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Trang ch\u1ee7\",\"item\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#website\",\"url\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/\",\"name\":\"App Mitunet\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"vi\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI | App Mitunet","description":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, \u1ee9ng d\u1ee5ng AI l\u00e0m m\u1edd \u1ea3nh hi\u1ec7u qu\u1ea3, c\u00e1ch l\u00e0m m\u1edd \u1ea3nh tr\u1ef1c tuy\u1ebfn tr\u00ean tr\u00ecnh duy\u1ec7t, gi\u1ea3i ph\u00e1p s\u1eeda h\u00ecnh \u1ea3nh...","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/","og_locale":"vi_VN","og_type":"article","og_title":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI | App Mitunet","og_description":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, \u1ee9ng d\u1ee5ng AI l\u00e0m m\u1edd \u1ea3nh hi\u1ec7u qu\u1ea3, c\u00e1ch l\u00e0m m\u1edd \u1ea3nh tr\u1ef1c tuy\u1ebfn tr\u00ean tr\u00ecnh duy\u1ec7t, gi\u1ea3i ph\u00e1p s\u1eeda h\u00ecnh \u1ea3nh...","og_url":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/","og_site_name":"App Mitunet","article_modified_time":"2026-08-15T11:17:51+00:00","og_image":[{"width":800,"height":600,"url":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/lam-mo-anh-online.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"\u01af\u1edbc t\u00ednh th\u1eddi gian \u0111\u1ecdc":"1 ph\u00fat"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/","url":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/","name":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI | App Mitunet","isPartOf":{"@id":"https:\/\/app.mitunet.com\/home\/#website"},"primaryImageOfPage":{"@id":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/#primaryimage"},"image":{"@id":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/#primaryimage"},"thumbnailUrl":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/lam-mo-anh-online.jpg","datePublished":"2026-08-12T15:32:55+00:00","dateModified":"2026-08-15T11:17:51+00:00","description":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, \u1ee9ng d\u1ee5ng AI l\u00e0m m\u1edd \u1ea3nh hi\u1ec7u qu\u1ea3, c\u00e1ch l\u00e0m m\u1edd \u1ea3nh tr\u1ef1c tuy\u1ebfn tr\u00ean tr\u00ecnh duy\u1ec7t, gi\u1ea3i ph\u00e1p s\u1eeda h\u00ecnh \u1ea3nh...","breadcrumb":{"@id":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/#breadcrumb"},"inLanguage":"vi","potentialAction":[{"@type":"ReadAction","target":["https:\/\/app.mitunet.com\/home\/lam-mo-anh\/"]}]},{"@type":"ImageObject","inLanguage":"vi","@id":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/#primaryimage","url":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/lam-mo-anh-online.jpg","contentUrl":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/lam-mo-anh-online.jpg","width":800,"height":600,"caption":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, l\u00e0m m\u1edd h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn"},{"@type":"BreadcrumbList","@id":"https:\/\/app.mitunet.com\/home\/lam-mo-anh\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Trang ch\u1ee7","item":"https:\/\/app.mitunet.com\/home\/"},{"@type":"ListItem","position":2,"name":"C\u00f4ng c\u1ee5 l\u00e0m m\u1edd \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng l\u00e0m m\u1edd h\u00ecnh \u1ea3nh b\u1eb1ng AI"}]},{"@type":"WebSite","@id":"https:\/\/app.mitunet.com\/home\/#website","url":"https:\/\/app.mitunet.com\/home\/","name":"App Mitunet","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/app.mitunet.com\/home\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"vi"}]}},"_links":{"self":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/pages\/607","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/comments?post=607"}],"version-history":[{"count":9,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/pages\/607\/revisions"}],"predecessor-version":[{"id":677,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/pages\/607\/revisions\/677"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/media\/676"}],"wp:attachment":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/media?parent=607"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}