Фоторамка с нейросетью

Коллеги, у Алекса есть проект "Фоторамка с нейросетью" на ESP8266. Купил дисплей на контроллере ILI9488. Не могу приживить, при запуске белый экран. WEB интерфейс работает. Хотелось решить эту проблему.

 

Комментарии

BRGN

✩✩✩✩✩✩✩
5 Апр 2025
2
6
@koss158,Не проверил перед отправкой, видимо редактор здесь не любит нижние подчеркивания)

Я в общем разобрался.
1. id приходит как текст, а не число
2. изменены некоторые пути обращения к api, к примеру раньше было"/key/api/v1/models", стало "/key/api/v1/pipelines", и так же вместо text2image стало pipeline
3. изменилось название полей num_image, негативного промта и model_ip на "numImages" , "negativePromptDecoder" "pipeline_id"

код немного с костылями, но рабочий. Костыль в моменте
if (json.parse(str.buf(), str.length())) {
return parse(state, json);
} else {
return parse(state, json);
FUS_LOG("Parse error");
}
при запросе id после стилей if не проходит и вылетает Parse error, но с добавлением return parse(state, json); в случае вылета по if всё отрабатывает нормально. Пользуйтесь, если получится исправить костыли и вообще улучшить код, буду рад)

new_api:
#pragma once
#include <Arduino.h>

// config
#define FUSION_HOST "api-key.fusionbrain.ai"
#define FUSION_PORT 443
#define FUSION_PERIOD 6000
#define FUSION_TRIES 5
#define FUS_LOG(x) Serial.println(x)

// #define GHTTP_HEADERS_LOG Serial
#include <GSON.h>
#include <GyverHTTP.h>

#include "StreamB64.h"
#include "tjpgd/tjpgd.h"

#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiClientSecureBearSSL.h>
#define FUSION_CLIENT BearSSL::WiFiClientSecure
#else
#include <WiFi.h>
#include <WiFiClientSecure.h>
#define FUSION_CLIENT WiFiClientSecure
#endif

class Kandinsky {
    typedef std::function<void(int x, int y, int w, int h, uint8_t* buf)> RenderCallback;
    typedef std::function<void()> RenderEndCallback;

    enum class State : uint8_t {
        GetModels,
        Generate,
        Status,
        GetStyles,
    };

   public:
    Kandinsky() {}
    Kandinsky(const String& apikey, const String& secret_key) {
        setKey(apikey, secret_key);
    }

    void setKey(const String& apikey, const String& secret_key) {
        if (apikey.length() && secret_key.length()) {
            _api_key = "Key " + apikey;
            _secret_key = "Secret " + secret_key;
        }
    }

    void onRender(RenderCallback cb) {
        _rnd_cb = cb;
    }

    void onRenderEnd(RenderEndCallback cb) {
        _end_cb = cb;
    }

    // 1, 2, 4, 8
    void setScale(uint8_t scale) {
        switch (scale) {
            case 1:
                _scale = 0;
                break;
            case 2:
                _scale = 1;
                break;
            case 4:
                _scale = 2;
                break;
            case 8:
                _scale = 3;
                break;
            default:
                _scale = 0;
                break;
        }
    }

    bool begin() {
        if (!_api_key.length()) return false;
        // изменён URL: теперь обращаемся к /key/api/v1/pipelines
        return request(State::GetModels, FUSION_HOST, "/key/api/v1/pipelines");
    }

    bool getStyles() {
        if (!_api_key.length()) return false;
        return request(State::GetStyles, "cdn.fusionbrain.ai", "/static/styles/key");
    }

    bool generate(Text query, uint16_t width = 512, uint16_t height = 512, Text style = "DEFAULT", Text negative = "") {
        status = "wrong config";
        if (!_api_key.length()) return false;
        if (!style.length()) return false;
        if (!query.length()) return false;
        if (!_id.length()) return false; // теперь _id – строка

        gson::string json;
        json.beginObj();
        json.addString(F("type"), F("GENERATE"));
        json.addString(F("style"), style);
        json.addString(F("negativePromptDecoder"), negative);
        json.addInt(F("width"), width);
        json.addInt(F("height"), height);
        json.addInt(F("numImages"), 1);
        {
            json.beginObj(F("generateParams"));
            json.addString(F("query"), query);
            json.endObj();
        }
        json.endObj(true);
        FUS_LOG("JSON being sent:");
        FUS_LOG(json);  // gson::string имеет перегрузку String()

        ghttp::Client::FormData data;
    
        // изменено название поля с model_id на pipeline_id
   
        data.add("pipeline_id", "", "", Value(_id));
        data.add("params", "blob", "application/json", json);

        uint8_t tries = FUSION_TRIES;
        while (tries--) {
            if (request(State::Generate, FUSION_HOST, "/key/api/v1/pipeline/run", "POST", &data)) {
                FUS_LOG("Gen request sent");
                status = "wait result";
                return true;
            } else {
                FUS_LOG("Gen request error");
                delay(2000);
            }
        }
        status = "gen request error";
        return false;
    }

    bool getImage() {
        if (!_api_key.length()) return false;
        if (!_uuid.length()) return false;
        FUS_LOG("Check status...");
        String url("/key/api/v1/pipeline/status/");
        url += _uuid;
        return request(State::Status, FUSION_HOST, url);
    }

    void tick() {
        if (_uuid.length() && millis() - _tmr >= FUSION_PERIOD) {
            _tmr = millis();
            getImage();
        }
    }

    String modelID() {
        return _id;
    }

    String styles = "DEFAULT;ANIME;UHD;KANDINSKY";
    String status = "idle";

   private:
    String _api_key;
    String _secret_key;
    String _uuid;
    uint8_t _scale = 0;
    uint32_t _tmr = 0;
    // изменён тип _id с int на String
    String _id;
    RenderCallback _rnd_cb = nullptr;
    RenderEndCallback _end_cb = nullptr;
    StreamB64* _stream = nullptr;

    // static
    static Kandinsky* self;

    static size_t jd_input_cb(JDEC* jdec, uint8_t* buf, size_t len) {
        if (self) {
            self->_stream->readBytes(buf, len);
        }
        return len;
    }

    static int jd_output_cb(JDEC* jdec, void* bitmap, JRECT* rect) {
        if (self && self->_rnd_cb) {
            self->_rnd_cb(rect->left, rect->top, rect->right - rect->left + 1, rect->bottom - rect->top + 1, (uint8_t*)bitmap);
        }
        return 1;
    }

    // system
    bool request(State state, Text host, Text url, Text method = "GET", ghttp::Client::FormData* data = nullptr) {
        FUSION_CLIENT client;
#ifdef ESP8266
        client.setBufferSizes(512, 512);
#endif
        client.setInsecure();
        ghttp::Client http(client, host.str(), FUSION_PORT);

        ghttp::Client::Headers headers;
        headers.add("X-Key", _api_key);
        headers.add("X-Secret", _secret_key);

        bool ok;
        if (data) {
       
            ok = http.request(url, method, headers, *data);
        } else {
            ok = http.request(url, method, headers);
        }
        if (!ok) {
            FUS_LOG("Request error");
            return false;
        }

        ghttp::Client::Response resp = http.getResponse();
        FUS_LOG("Response code: " + String(resp.code()));
        if (resp && resp.code() >= 200 && resp.code() < 300) {
            if (state == State::Status) {
                bool ok = parseStatus(resp.body());
                http.flush();
                return ok;
            } else {
                gtl::stack_uniq<uint8_t> str;
                resp.body().writeTo(str);
                gson::Parser json;

                if (json.parse(str.buf(), str.length())) {
                    return parse(state, json);
                } else {
                    return parse(state, json);
                    FUS_LOG("Parse error");
                }
            }
        } else {
            http.flush();
            FUS_LOG("Error" + String(resp.code()));
            FUS_LOG("Response error");
        }
        return false;
    }

    bool parseStatus(Stream& stream) {
        bool found = 0;
        bool insideResult = false;
        while (stream.available()) {
            stream.readStringUntil('\"');
            String key = stream.readStringUntil('\"');
            if (key == "result") {
                insideResult = true;
                continue;
            }
            if (insideResult && key == "files") {
            found = true;
            break;
            }
            stream.readStringUntil('\"');
            String val = stream.readStringUntil('\"');
            if (!key.length() || !val.length()) break;

            if (key == "status") {
                switch (Text(val).hash()) {
                    case SH("INITIAL"):
                    case SH("PROCESSING"):
                        return true;
                    case SH("DONE"):
                        _uuid = "";
                        break;
                    case SH("FAIL"):
                        _uuid = "";
                        status = "gen fail";
                        return false;
                }
            }
        }
        if (found) {
            stream.readStringUntil('\"');
            uint8_t* workspace = new uint8_t[TJPGD_WORKSPACE_SIZE];
            if (!workspace) {
                FUS_LOG("allocate error");
                return false;
            }

            JDEC jdec;
            jdec.swap = 0;
            JRESULT jresult = JDR_OK;
            StreamB64 sb64(stream);
            _stream = &sb64;
            self = this;

            jresult = jd_prepare(&jdec, jd_input_cb, workspace, TJPGD_WORKSPACE_SIZE, 0);

            if (jresult == JDR_OK) {
                jresult = jd_decomp(&jdec, jd_output_cb, _scale);

                if (jresult == JDR_OK) {
                    if (_end_cb) _end_cb();
                } else {
                    FUS_LOG("jdec error");
                }
            } else {
                FUS_LOG("jdec error");
            }

            self = nullptr;
            delete[] workspace;

            status = jresult == JDR_OK ? "gen done" : "jpg error";
            return jresult == JDR_OK;
        }
        return true;
    }

    bool parse(State state, gson::Parser& json) {
 
        switch (state) {
            case State::GetStyles:
           
                styles = "";
                for (int i = 0; i < (int)json.rootLength(); i++) {
                    if (i) styles += ';';
                    json[i]["name"].addString(styles);
                }
                if (styles.length()) return true;
                break;

            case State::GetModels:
           
           
                json[0]["id"].toString(_id);
                if (_id.length()) return true;
                break;

            case State::Generate:
                _tmr = millis();
                json["uuid"].toString(_uuid);
                if (_uuid.length()) return true;
                break;

            default:
                break;
        }
        return false;
    }
};

Kandinsky* Kandinsky::self [B]attribute[/B] ((weak)) = nullptr;
 
Изменено:

vadim352627

★✩✩✩✩✩✩
25 Янв 2025
16
17
Переделал немного вашу версию, и получилось так. Костыль вроде как решен.
Kandinsky.h:
#pragma once
#include <Arduino.h>
// config
#define FUSION_HOST "api-key.fusionbrain.ai"
#define FUSION_PORT 443
#define FUSION_PERIOD 6000
#define FUSION_TRIES 5
#define FUS_LOG(x) Serial.println(x)
// #define GHTTP_HEADERS_LOG Serial
#include <GSON.h>
#include <GyverHTTP.h>
#include "StreamB64.h"
#include "tjpgd/tjpgd.h"
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiClientSecureBearSSL.h>
#define FUSION_CLIENT BearSSL::WiFiClientSecure
#else
#include <WiFi.h>
#include <WiFiClientSecure.h>
#define FUSION_CLIENT WiFiClientSecure
#endif
class Kandinsky {
    typedef std::function<void(int x, int y, int w, int h, uint8_t* buf)> RenderCallback;
    typedef std::function<void()> RenderEndCallback;
    enum class State : uint8_t {
        GetModels,
        Generate,
        Status,
        GetStyles,
    };
   public:
    Kandinsky() {}
    Kandinsky(const String& apikey, const String& secret_key) {
        setKey(apikey, secret_key);
    }
    void setKey(const String& apikey, const String& secret_key) {
        if (apikey.length() && secret_key.length()) {
            _api_key = "Key " + apikey;
            _secret_key = "Secret " + secret_key;
        }
    }
    void onRender(RenderCallback cb) {
        _rnd_cb = cb;
    }
    void onRenderEnd(RenderEndCallback cb) {
        _end_cb = cb;
    }
    // 1, 2, 4, 8
    void setScale(uint8_t scale) {
        switch (scale) {
            case 1: _scale = 0; break;
            case 2: _scale = 1; break;
            case 4: _scale = 2; break;
            case 8: _scale = 3; break;
            default: _scale = 0; break;
        }
    }
    bool begin() {
        if (!_api_key.length()) return false;
        return request(State::GetModels, FUSION_HOST, "/key/api/v1/pipelines");
    }
    bool getStyles() {
        if (!_api_key.length()) return false;
        return request(State::GetStyles, "cdn.fusionbrain.ai", "/static/styles/web");
    }
    bool generate(Text query, uint16_t width = 512, uint16_t height = 512, Text style = "DEFAULT", Text negative = "") {
        status = "wrong config";
        if (!_api_key.length()) return false;
        if (!style.length()) return false;
        if (!query.length()) return false;
        if (!_id.length()) return false;
        gson::string json;
        json.beginObj();
        json.addString(F("type"), F("GENERATE"));
        json.addString(F("style"), style);
        json.addString(F("negativePromptDecoder"), negative);
        json.addInt(F("width"), width);
        json.addInt(F("height"), height);
        json.addInt(F("numImages"), 1);
        json.beginObj(F("generateParams"));
        json.addString(F("query"), query);
        json.endObj();
        json.endObj(true);
        // FUS_LOG("JSON being sent:");
        // FUS_LOG(json);
        ghttp::Client::FormData data;
        data.add("pipeline_id", "", "", Value(_id));
        data.add("params", "blob", "application/json", json);
        uint8_t tries = FUSION_TRIES;
        while (tries--) {
            if (request(State::Generate, FUSION_HOST, "/key/api/v1/pipeline/run", "POST", &data)) {
                FUS_LOG("Gen request sent");
                status = "wait result";
                return true;
            } else {
                FUS_LOG("Gen request error");
                delay(2000);
            }
        }
        status = "gen request error";
        return false;
    }
    bool getImage() {
        if (!_api_key.length()) return false;
        if (!_uuid.length()) return false;
        FUS_LOG("Check status...");
        String url("/key/api/v1/pipeline/status/");
        url += _uuid;
        return request(State::Status, FUSION_HOST, url);
    }
    void tick() {
        if (_uuid.length() && millis() - _tmr >= FUSION_PERIOD) {
            _tmr = millis();
            getImage();
        }
    }
    String modelID() { return _id; }
    String styles = "";
    String status = "";
   private:
    String _api_key;
    String _secret_key;
    String _uuid;
    uint8_t _scale = 0;
    uint32_t _tmr = 0;
    String _id;
    RenderCallback _rnd_cb = nullptr;
    RenderEndCallback _end_cb = nullptr;
    StreamB64* _stream = nullptr;
    // static
    static Kandinsky* self;
    static size_t jd_input_cb(JDEC* jdec, uint8_t* buf, size_t len) {
        if (self) {
            self->_stream->readBytes(buf, len);
        }
        return len;
    }
    static int jd_output_cb(JDEC* jdec, void* bitmap, JRECT* rect) {
        if (self && self->_rnd_cb) {
            self->_rnd_cb(rect->left, rect->top, rect->right - rect->left + 1, rect->bottom - rect->top + 1, (uint8_t*)bitmap);
        }
        return 1;
    }
    // system
    bool request(State state, Text host, Text url, Text method = "GET", ghttp::Client::FormData* data = nullptr) {
        FUSION_CLIENT client;
#ifdef ESP8266
        client.setBufferSizes(512, 512);
#endif
        client.setInsecure();
        ghttp::Client http(client, host.str(), FUSION_PORT);
        ghttp::Client::Headers headers;
        headers.add("X-Key", _api_key);
        headers.add("X-Secret", _secret_key);
        bool ok = data ? http.request(url, method, headers, *data)
                       : http.request(url, method, headers);
        if (!ok) {
            FUS_LOG("Request error");
            return false;
        }
        ghttp::Client::Response resp = http.getResponse();
        // FUS_LOG("Response code: " + String(resp.code()));
        if (resp && resp.code() >= 200 && resp.code() < 300) {
            if (state == State::Status) {
                bool ok = parseStatus(resp.body());
                http.flush();
                return ok;
            } else {
                gtl::stack_uniq<uint8_t> str;
                resp.body().writeTo(str);
                gson::Parser json;
                if (!json.parse(str.buf(), str.length())) {
                    FUS_LOG("Parse error");
                    return false;
                }
                return parse(state, json);
            }
        } else {
            http.flush();
            FUS_LOG("Error" + String(resp.code()));
            FUS_LOG("Response error");
        }
        return false;
    }
    bool parseStatus(Stream& stream) {
        bool found = false;
        bool insideResult = false;
        while (stream.available()) {
            stream.readStringUntil('"');
            String key = stream.readStringUntil('"');
            if (key == "result") {
                insideResult = true;
                continue;
            }
            if (insideResult && key == "files") {
                found = true;
                break;
            }
            stream.readStringUntil('"');
            String val = stream.readStringUntil('"');
            if (!key.length() || !val.length()) break;
            if (key == "status") {
                switch (Text(val).hash()) {
                    case SH("INITIAL"):
                    case SH("PROCESSING"):
                        return true;
                    case SH("DONE"):
                        _uuid = "";
                        break;
                    case SH("FAIL"):
                        _uuid = "";
                        status = "gen fail";
                        return false;
                }
            }
        }
        if (found) {
            stream.readStringUntil('"');
            uint8_t* workspace = new uint8_t[TJPGD_WORKSPACE_SIZE];
            if (!workspace) {
                FUS_LOG("allocate error");
                return false;
            }
            JDEC jdec;
            jdec.swap = 0;
            JRESULT jresult = JDR_OK;
            StreamB64 sb64(stream);
            _stream = &sb64;
            self = this;
            jresult = jd_prepare(&jdec, jd_input_cb, workspace, TJPGD_WORKSPACE_SIZE, 0);
            if (jresult == JDR_OK) {
                jresult = jd_decomp(&jdec, jd_output_cb, _scale);
                if (jresult == JDR_OK && _end_cb) _end_cb();
            } else {
                FUS_LOG("jdec error");
            }
            self = nullptr;
            delete[] workspace;
            status = jresult == JDR_OK ? "gen done" : "jpg error";
            return jresult == JDR_OK;
        }
        return true;
    }
    bool parse(State state, gson::Parser& json) {
        switch (state) {
            case State::GetStyles:
                styles = "";
                for (int i = 0; i < (int)json.rootLength(); i++) {
                    if (i) styles += ';';
                    json[i]["name"].addString(styles);
                }
                return styles.length();
            case State::GetModels:
                json[0]["id"].toString(_id);
                if (_id.length()) return true;
                break;
            case State::Generate:
                _tmr = millis();
                json["uuid"].toString(_uuid);
                if (_uuid.length()) return true;
                break;
            default:
                break;
        }
        return false;
    }
};
Kandinsky* Kandinsky::self [B]attribute[/B]((weak)) = nullptr;
В конце должно быть
Kandinsky* Kandinsky::self __attribute__((weak)) = nullptr;, не понимаю почему он ставит "B"

UPD. Внёс в код правки для отображения большего количества стилей.
 
Изменено:

vadim352627

★✩✩✩✩✩✩
25 Янв 2025
16
17
@sgrock, У меня автогенерация работает, если вы используете мою версию Kandinsky, значит проблема явно в других файлах.
 

jaxkz

✩✩✩✩✩✩✩
23 Сен 2024
6
3
@vadim352627, у KNLL, тоже интересный проект на esp32 с возможностью сохранения изображений на SD карточку. Жаль тоже здесь уже давно не появлялся...
 
  • Лойс +1
Реакции: vadim352627

f1f2

✩✩✩✩✩✩✩
1 Авг 2022
1
0
Судя по лайкам, рамку пользуют 5 человек от силы...
Уже больше! )
Красивый, но капризный проект!
Больше года с ним, уже хотел выбросить (разобрать на детали).
Спасибо ребятам здесь, вернули к жизни!
Работает! Правда иногда, совсем не часто, ... jpg error! (
Но стол и рабочее пространство украшает!
 

Volkur

✩✩✩✩✩✩✩
26 Фев 2021
2
2
Здравствуйте уважаемые форумчане. Помогите если возможно. Собрал рамку на ILI9488 залил рабочий код, с первого раза заработало, насмотрелся, через час отключил от питания и всё пропало. Сколько не перегружал, менял и Token и Secret результат один и тот же. Подскажите в чем проблема.
 

Вложения

  • Лойс +1
Реакции: LiDomPRO

LikePod

✩✩✩✩✩✩✩
15 Сен 2023
13
0
@vadim352627,Добрый день. Спасибо, фоторамка опять заработала! Но стилей опять стало 4.. (141,142 строки - текст удалил, но не помогло.. У Вас сколько стилей доступно?
 

dvdiamond

★✩✩✩✩✩✩
15 Авг 2024
9
10
@Volkur, так было со старой библиотекой. Но раз работало, значит уже новая. Тогда загадка. Может, бан. Попробуйте прошить заново. И с другого ip зайти в инет.
 

Sturdm

✩✩✩✩✩✩✩
4 Июн 2021
5
2
@vadim352627,
key на web поменян в коде Кондинский?
поменять здесь

bool getStyles() {
if (!_api_key.length()) return false;
return request(State::GetStyles, "cdn.fusionbrain.ai", "/static/styles/key");
 
  • Лойс +1
Реакции: LikePod

LikePod

✩✩✩✩✩✩✩
15 Сен 2023
13
0
@Sturdm, Вы правы. Если поправить 141, 142 строчки, и заменить key на web - все работает как надо!
 

vadim352627

★✩✩✩✩✩✩
25 Янв 2025
16
17
Я обновил свой последний код для отображение большего кол-ва стилей.
 

Volkur

✩✩✩✩✩✩✩
26 Фев 2021
2
2
Спасибо всем, разобрался. Затык произошел на границе 07.04. внес изменения из форума в библиотеку все заработало. Всем СПАСИБО!!!
 
  • Лойс +1
Реакции: vadim352627

AlexeyNov

✩✩✩✩✩✩✩
15 Апр 2025
5
3
Перестала компилироваться прошивка в ArduinoIDE 2.3.6. Выдает ошибку: error: no match for call to '(sets::Build) ()'
Подскажите, как с этим бороться?
 

SoftFelix

★✩✩✩✩✩✩
16 Фев 2020
56
16
Перестала компилироваться прошивка в ArduinoIDE 2.3.6.
А на 2.3.4 ? (2.3.5. лучше не ставить, она конкретно глючная)
Выдает ошибку: error: no match for call to '(sets::Build) ()'
Подскажите, как с этим бороться?
GyverLibs точно все последние-актуальные?
 

AlexeyNov

✩✩✩✩✩✩✩
15 Апр 2025
5
3
на 2.3.4 ? (2.3.5. лучше не ставить, она конкретно глючная)
На 2.3.4 компилировалась. После установки 2.3.6 дала ошибку, откат обратно на 2.3.4 не спасает. Ошибка осталась.

GyverLibs точно все последние-актуальные?
Скачал с GitHub - AlexGyver/AiFrame: Фоторамка с нейросетью
 

SoftFelix

★✩✩✩✩✩✩
16 Фев 2020
56
16
Не, так дело не пойдёт. У него там папка библиотек пол года не обновлялась. Идём в Управление библиотеками, выбираем тип "Обновляемый", ждём и обновляем всё что предложит вместе с зависимостями! И только потом пытаемся компилировать.
 
  • Лойс +1
Реакции: AlexeyNov

Zag41

✩✩✩✩✩✩✩
5 Апр 2025
1
0
Добрый день. Есть у кого-нибудь нормально работающий вариант ESP8266_ili9488? Проблема со стилями, рисует как будто смесь нескольких стилей, какие-то уродцы получаются ) Явно не так как на сайте
 

Вложения

SoftFelix

★✩✩✩✩✩✩
16 Фев 2020
56
16
@AlexeyNov, могу так сказать: исходник с Гитхаба с последними-актуальными библиотеками компилируется без проблем в 2.3.4 и с выбором платы "NodeMCU 1.0 (ESP-12E Module)".

Но это всё равно сейчас работать не будет. Библиотеку Кандинский нужно заменить на эту с обязательным фиксом руками последней строчки.
 

AlexeyNov

✩✩✩✩✩✩✩
15 Апр 2025
5
3
Библиотеку Кандинский нужно заменить на эту с обязательным фиксом руками последней строчки.
Так я собственно говоря из-за этого и начал переустанавливать программу. У меня рамка уже давно работает. Несколько раз правил программ под новые требования. А вот сейчас что-то взбрыкнула компиляция.
Ну да ладно, попробую сам разобраться в чем дело. Если получится - напишу решение.
 
  • Лойс +1
Реакции: SoftFelix

LikePod

✩✩✩✩✩✩✩
15 Сен 2023
13
0
@Syrion,
Добрый день! А можете пожалуйста поделиться исходниками? Тоже захотелось часы прикрутить...
 

AlexeyNov

✩✩✩✩✩✩✩
15 Апр 2025
5
3
Заметил, что картинка на изменение стиля не реагирует. Задал стиль "Пикассо", там вообще глаз на ухе должен рисоваться, но выдает нормальный сюжет.
Это только у меня так?
 
  • Лойс +1
Реакции: vadim352627 и LikePod