o3-mini-high - prototyp MS Paint gotowy w 19s

o3-mini-high - prototyp MS Paint gotowy w 19s
pradoslaw
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
  • Postów: 216
4

To są zaledwie 3 prompty:

  1. Zbuduj MS Paint z zaawansowanymi ustawieniami. Użyj CSS, JS i HTML w jednym pliku HTML - 19s
  2. Dodaj funkcje "cofnij" "wróć" zmianę - 32s
  3. Niech UI programu korzysta z kolorystyki serwisu 4programmers.net 32s

Wszystko zadziałało przy pierwszej próbie odpalenia. Zero bugów. Czy to nie jest gigantyczny postęp?

Markup 2025-02-13 at 13.30.37.png

Kopiuj
<!DOCTYPE html>
<html lang="pl">
<head>
  <meta charset="UTF-8" />
  <title>Advanced MS Paint z Cofnij/Wróć</title>
  <style>
    /* Ogólna kolorystyka inspirowana 4programmers.net */
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background-color: #ecf0f1; /* jasny szary */
    }
    /* Pasek narzędzi – ciemne tło z jasnym tekstem */
    #toolbar {
      background-color: #2c3e50; /* ciemny niebiesko-szary */
      color: #ecf0f1; /* jasny tekst */
      padding: 10px;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
    }
    #toolbar > * {
      margin: 5px;
    }
    #toolbar label {
      margin-right: 5px;
    }
    /* Stylizacja pól wyboru, inputów i select */
    #toolbar input[type="color"],
    #toolbar input[type="range"],
    #toolbar select {
      padding: 5px;
      border-radius: 3px;
      border: none;
    }
    /* Przyciski – pomarańczowe akcenty */
    #toolbar button {
      background-color: #d35400; /* intensywny pomarańczowy */
      color: #ecf0f1;
      border: none;
      padding: 8px 12px;
      cursor: pointer;
      border-radius: 3px;
      transition: background-color 0.3s;
    }
    #toolbar button:hover {
      background-color: #e67e22;
    }
    /* Kontener płótna */
    #canvasContainer {
      position: relative;
      border: 1px solid #bdc3c7;
      margin: 10px auto;
      background-color: #fff;
      width: 800px;
      height: 600px;
    }
    canvas {
      position: absolute;
      left: 0;
      top: 0;
    }
    /* Główny canvas (na spodzie) */
    #canvas {
      z-index: 1;
    }
    /* Tymczasowy canvas do podglądu kształtów */
    #tempCanvas {
      z-index: 2;
    }
  </style>
</head>
<body>
  <div id="toolbar">
    <label for="tool">Narzędzie:</label>
    <select id="tool">
      <option value="pencil">Ołówek</option>
      <option value="line">Linia</option>
      <option value="rectangle">Prostokąt</option>
      <option value="circle">Koło</option>
      <option value="eraser">Gumka</option>
    </select>

    <label for="brushSize">Grubość pędzla:</label>
    <input type="range" id="brushSize" min="1" max="50" value="5" />

    <label for="strokeColor">Kolor obrysu:</label>
    <input type="color" id="strokeColor" value="#000000" />

    <label for="fillColor">Kolor wypełnienia:</label>
    <input type="color" id="fillColor" value="#000000" />

    <label for="fillShape">Wypełnienie:</label>
    <input type="checkbox" id="fillShape" />

    <button id="clearCanvas">Wyczyść</button>
    <button id="saveCanvas">Zapisz</button>
    <button id="undo">Cofnij</button>
    <button id="redo">Wróć</button>
  </div>

  <div id="canvasContainer">
    <canvas id="canvas" width="800" height="600"></canvas>
    <canvas id="tempCanvas" width="800" height="600"></canvas>
  </div>

  <script>
    // Pobierz elementy i konteksty
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    const tempCanvas = document.getElementById('tempCanvas');
    const tempCtx = tempCanvas.getContext('2d');

    const toolSelect = document.getElementById('tool');
    const brushSize = document.getElementById('brushSize');
    const strokeColor = document.getElementById('strokeColor');
    const fillColor = document.getElementById('fillColor');
    const fillShape = document.getElementById('fillShape');
    const clearCanvasBtn = document.getElementById('clearCanvas');
    const saveCanvasBtn = document.getElementById('saveCanvas');
    const undoBtn = document.getElementById('undo');
    const redoBtn = document.getElementById('redo');

    let drawing = false;
    let currentTool = toolSelect.value;
    let startX = 0, startY = 0;

    // Stosy do cofania i ponawiania zmian
    let undoStack = [];
    let redoStack = [];

    // Zapisz aktualny stan płótna
    function pushState() {
      // Nowe działanie – czyścimy stos do ponawiania
      redoStack = [];
      // Zapisujemy aktualny stan (ImageData)
      undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
    }

    function undo() {
      if (undoStack.length > 0) {
        // Zapisz bieżący stan do stosu "redo"
        redoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
        // Przywróć ostatni stan
        const state = undoStack.pop();
        ctx.putImageData(state, 0, 0);
      }
    }

    function redo() {
      if (redoStack.length > 0) {
        // Zapisz bieżący stan do stosu "undo"
        undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
        // Przywróć ostatni stan ze stosu "redo"
        const state = redoStack.pop();
        ctx.putImageData(state, 0, 0);
      }
    }

    // Inicjalizacja – zapis pustego płótna
    pushState();

    // Aktualizacja narzędzia przy zmianie wyboru
    toolSelect.addEventListener('change', function () {
      currentTool = this.value;
    });

    // Obsługa zdarzeń myszy na tymczasowym canvasie
    tempCanvas.addEventListener('mousedown', startDrawing);
    tempCanvas.addEventListener('mousemove', draw);
    tempCanvas.addEventListener('mouseup', finishDrawing);
    tempCanvas.addEventListener('mouseout', finishDrawing);

    function getMousePos(e) {
      const rect = tempCanvas.getBoundingClientRect();
      return {
        x: e.clientX - rect.left,
        y: e.clientY - rect.top,
      };
    }

    function startDrawing(e) {
      drawing = true;
      const pos = getMousePos(e);
      startX = pos.x;
      startY = pos.y;

      // Dla narzędzi typu "pencil" i "eraser" zaczynamy natychmiast ścieżkę
      if (currentTool === 'pencil' || currentTool === 'eraser') {
        ctx.beginPath();
        ctx.moveTo(startX, startY);
      }
    }

    function draw(e) {
      if (!drawing) return;
      const pos = getMousePos(e);
      const mouseX = pos.x;
      const mouseY = pos.y;

      if (currentTool === 'pencil') {
        ctx.lineTo(mouseX, mouseY);
        ctx.strokeStyle = strokeColor.value;
        ctx.lineWidth = brushSize.value;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.stroke();
      } else if (currentTool === 'eraser') {
        ctx.globalCompositeOperation = 'destination-out';
        ctx.lineTo(mouseX, mouseY);
        ctx.strokeStyle = 'rgba(0,0,0,1)';
        ctx.lineWidth = brushSize.value;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.stroke();
        ctx.globalCompositeOperation = 'source-over';
      } else {
        // Dla narzędzi kształtów – czyścimy podgląd i rysujemy tymczasowy kształt
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        tempCtx.lineWidth = brushSize.value;
        tempCtx.strokeStyle = strokeColor.value;
        tempCtx.fillStyle = fillColor.value;
        tempCtx.lineCap = 'round';
        tempCtx.lineJoin = 'round';

        if (currentTool === 'line') {
          tempCtx.beginPath();
          tempCtx.moveTo(startX, startY);
          tempCtx.lineTo(mouseX, mouseY);
          tempCtx.stroke();
        } else if (currentTool === 'rectangle') {
          const width = mouseX - startX;
          const height = mouseY - startY;
          if (fillShape.checked) {
            tempCtx.fillRect(startX, startY, width, height);
          }
          tempCtx.strokeRect(startX, startY, width, height);
        } else if (currentTool === 'circle') {
          const radius = Math.sqrt(Math.pow(mouseX - startX, 2) + Math.pow(mouseY - startY, 2));
          tempCtx.beginPath();
          tempCtx.arc(startX, startY, radius, 0, Math.PI * 2);
          if (fillShape.checked) {
            tempCtx.fill();
          }
          tempCtx.stroke();
        }
      }
    }

    function finishDrawing(e) {
      if (!drawing) return;
      drawing = false;
      const pos = getMousePos(e);
      const mouseX = pos.x;
      const mouseY = pos.y;

      // Dla narzędzi kształtów – przenosimy tymczasowy kształt na główny canvas
      if (currentTool === 'line' || currentTool === 'rectangle' || currentTool === 'circle') {
        ctx.lineWidth = brushSize.value;
        ctx.strokeStyle = strokeColor.value;
        ctx.fillStyle = fillColor.value;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';

        if (currentTool === 'line') {
          ctx.beginPath();
          ctx.moveTo(startX, startY);
          ctx.lineTo(mouseX, mouseY);
          ctx.stroke();
        } else if (currentTool === 'rectangle') {
          const width = mouseX - startX;
          const height = mouseY - startY;
          if (fillShape.checked) {
            ctx.fillRect(startX, startY, width, height);
          }
          ctx.strokeRect(startX, startY, width, height);
        } else if (currentTool === 'circle') {
          const radius = Math.sqrt(Math.pow(mouseX - startX, 2) + Math.pow(mouseY - startY, 2));
          ctx.beginPath();
          ctx.arc(startX, startY, radius, 0, Math.PI * 2);
          if (fillShape.checked) {
            ctx.fill();
          }
          ctx.stroke();
        }
        // Czyścimy warstwę podglądu
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
      }
      
      // Po zakończeniu rysowania zapisujemy aktualny stan dla cofania
      pushState();
    }

    // Czyścimy canvas po kliknięciu przycisku "Wyczyść"
    clearCanvasBtn.addEventListener('click', function () {
      pushState(); // Zapisz stan przed czyszczeniem
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      pushState(); // Zapisz stan pustego płótna
    });

    // Zapisujemy rysunek jako obrazek
    saveCanvasBtn.addEventListener('click', function () {
      // Łączymy główny canvas z ewentualnym podglądem
      const mergedCanvas = document.createElement('canvas');
      mergedCanvas.width = canvas.width;
      mergedCanvas.height = canvas.height;
      const mergedCtx = mergedCanvas.getContext('2d');
      mergedCtx.drawImage(canvas, 0, 0);
      mergedCtx.drawImage(tempCanvas, 0, 0);
      const link = document.createElement('a');
      link.download = 'my_painting.png';
      link.href = mergedCanvas.toDataURL();
      link.click();
    });

    // Przypisujemy funkcje cofania i ponawiania do przycisków
    undoBtn.addEventListener('click', undo);
    redoBtn.addEventListener('click', redo);
  </script>
</body>
</html>
loza_prowizoryczna
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1628
0
pradoslaw napisał(a):

Wszystko zadziałało przy pierwszej próbie odpalenia. Zero bugów. Czy to nie jest gigantyczny postęp?

Jeśli w Javascripcie i HTMLu to aktualnie regres bo tego uczą na bootcampach.

AN
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 989
0

No ogólnie spoko tylko to jest bardzo odtwórcze i nieskomplikowane.

Co nie zmienia faktu, że jakby ktoś pokazał Ci takie "AI" 7 lat temu to każdemu oczy by wyskoczyły :D Ale teraz już to nie robi takiego wrażenia bo się człowiek przyzwyczaił

Kokoniłaj
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 190
1

Super. Jestem ciekawy czy poradzi sobie z dodaniem opcji rysowania prostokątów z gradientem liniowym jako wypełnieniem wraz z możliwością rotacji tego gradientu.

pradoslaw
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
  • Postów: 216
3

@Kokoniłaj tak wygląda efekt jednego dodatkowego prompta:
Markup 2025-02-13 at 14.22.47.png

opiszon
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 832
13

Widzę zachwyt nad gradientami co powoduje że muszę zadać pytanie:

Czy to ai projektuje aktualny layout oraz pisze kod 4p?

SZ
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 216
1

A powiedz mu żeby dodał takiego painta do forum 4p, jako opcja dodania posta do forum. Że wybieram sobie opcje, czy chce odpowiedzieć tekstem, czy obrazkiem.

KM
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 111
5

OGŁASZAM KONIEC ELDORADOimage

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
1

Progres jest ogromny, ja jednak wyskoczyłem ponad promot o jakieś jednostronicowe toole i chciałem zrobić pełnoprawną aplikacje.
Założenie API w node.js, a na froncie sveltekit
API korzysta już z gotowej bazy danych.

Generowanie backendu bez napisania ani jednej linijki kodu 2 andpointy z get, wygenerował działające

Z front endem sobie nie poradził nie udało mi się stworzyć gotowego i działającego. Nie mam jednak złudzeń, że to kwestia czasu i będzie potrafił

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
3

No jeszcze dwie minuty i by miał wszystkie funkcje gimpa. Ale sklonowanie repo zajmuje mniej. Jest w stanie zrobić coś co jeszcze nie istnieje?

Niech doda odpalanie kodu do postów na 4p i ponaprawia tu bugi.

RequiredNickname
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 646
2

a teraz poproś go aby zrobił ficzer w twoim spaghetti kozie lub naprawił buga z produkcji 🙃

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
2

Ja patrzę na to trochę inaczej, teraz Chińczycy wyszli z deepseek i mocno namieszali.
Wszyscy liczący się w branży AI nagle mocno przyspieszyli.
Mogę oczywiście punktować czego AI jeszcze nie potrafi, ale IMO to kwestia czasu. Trend jest nakreślony i zaczynają jak zawsze najwieksi: https://www.linkedin.com/posts/andy-jassy-8b1615_one-of-the-most-tedious-but-critical-tasks-activity-7232374162185461760-AdSz/?utm_source%3Dshare%26utm_medium%3Dmember_ios

Idą za tym tak ogromne pieniądze, są pierwsze efekty więc to nie pytanie czy? a kiedy? Możemy heheszkowac. Możemy też poważnie podejść do tematu i poobserwować jak to wpływa na inne branże.

Viralowa reklama VOLVO zrobiona w niecałe 24 godziny przez AI

https://m.youtube.com/watch?v=VYZrsEyiEFs

Gdzie eksperci szacują że budżet na jej wykonanie tradycyjnie to może dochodzić do 0,5 mln USD

Biznes to zauważa i liczy pieniądze.

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
0

@szok Fiimik z linkowanego posta nie odnosi się do opisywanego przypadku, ale patrząc na prompt:

screenshot-20250214100723.png

Gdzieś mi się kojarzy, że to włąsnie była jakas stara wersja JAVY, i musieli to upgradeować bo to uniemozliwiało im rozwój.

Manna5
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Kraków
  • Postów: 667
0
pradoslaw napisał(a):

Zero bugów.

Na pewno? Testowałeś różne nietypowe sytuacje?

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
1
Manna5 napisał(a):
pradoslaw napisał(a):

Zero bugów.

Na pewno? Testowałeś różne nietypowe sytuacje?

Przeglądałem kod i nie znalazłem nic oczywistego, ale na przykład historia modyfikacji jest przechowywana na bogato - każde kliknięcie to snapshot całego canvasa do pamięci co zwiększa zużycie pamięci o 2MB. Kilkanaście sekund zabawy i zakładka wykorzystuje już 250MB pamięci.
Nie ma limitu cofania co oznacza że w pewnym momencie mogą się pojawić problemy z pamięcią.

Oczywiście to jest do poprawienia przez AI, ale trzeba to przetestować i wpaść na to samemu i wiedzieć o co zapytać. Ciekawe kiedy AI będzie w stanie myśleć o wszystkich edge case'ach.

Panczo napisał(a):

Viralowa reklama VOLVO zrobiona w niecałe 24 godziny przez AI

https://m.youtube.com/watch?v=VYZrsEyiEFs

Gdzie eksperci szacują że budżet na jej wykonanie tradycyjnie to może dochodzić do 0,5 mln USD

Biznes to zauważa i liczy pieniądze.

Niestety to głównie oznacza większą ilość shitu w necie, już teraz co 20 materiał na jaki trafiam jest wygenerowany przez AI, w sklepach pojawiają się obrazy i książki wygenerowane przez AI. Niedługo utoniemy w shit contencie, mało kto będzie chciał się uczyć robić tego wszystkiego skoro lata mu zajmie dojście do poziomu AI a potem miesiące mu zajmie zrobienie coś co AI robi w sekundę więc umiejętności zaczną zanikać.
Idiocracy ziści się dużo szybciej niż przewidywano.

Nie cieszy mnie rozwój AI nie tylko dlatego że zagraża mojej pracy i sprawi że moje hobby będzie jałowe i mało ekscytujące, ale głównie dlatego że nie umiem sobie wyobrazić jednego scenariusza w którym wynalezienie AGI skończy się dobrze dla ludzkości.

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
1

@obscurity nie jestem idealistą i nie patrzę na AI bezkrytycznie, tyle ile problemów rozwiąże to pewnie tyle samo stworzy.
Sam osobiście jestem "przerażony" że już teraz dostaje kompentecje których nie byłbym wstanie opanować samemu, jak choćby tworzenie grafik, nie ma w tym kierunku żadnego talentu, a tak bez problemu jestem wstanie to zrobić.

Nie patrzę na to w kategoriach emocji, to czy AGI faktycznie powstanie jest pytaniem otwartym, ale to, że w niedalekiej przyszłości programiści będą zastępowani przez AI wydaje mi się całkiem realne. Nie że nie będą potrzebni, ale będzie ich potrzeba zwyczajnie mniej. To oczywiście mój punkt widzenia, ale wynika z doniesień prasowych o tym co robią najwięksi gracze. Już choćby to że MS "daje" copilota za darmo odczytuje jako krok w kierunku zastapienia programistów. W końcu dane z internetu "się skończyły", to bedą analizować kody użytkowników.

Dlatego nie patrzę na AI na miejsce w którym jest teraz, a na to gdzie może być.

Rozmawiam też z właścicielami firm i Ci którzy poszli w AI notują wzrosty w swoich biznesach, szczególnie agencje marketingowe. Jeden własciciel mi podał swój case: pracownik pewną rzecz (nie napiszę jaką, bo szczerze zapomniałem, a nie jestem marketingowcem więc nie chcę się wygłupić) robił od 4-5 dni i klient za to płacił 6k. Dzięki AI robi dokładnie to samo w niecałą godzinę...

Moi klienci również pytają kiedy wprowadzę AI w swoich produktach. I sam mocno ogarniam temat. Z mojego podwórka (a raczej mojej spółki) chce wprowadzić nowy produkt i wyszło mi że potrzebuję do tego 2 programistów, no i liczę: uprośćmy że na tą 2 wydam 50k miesięcznie co rocznie daje 600k, to zastanawiam się, czy nie wyposażyć mój zespół w AI do wspomagania/generowania kodu i zapłacić za to AI niech będzie 100k rocznie, ale ogarnę to tymi samymi zasobami którymi dysponuje. I wiem, że nie jestem odosobniony w takim myśleniu.

Teraz pozostaje pytanie, czy negujemy AI w biznesie, czy wsiadamy do tego pociągu.

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
2
Panczo napisał(a):

pracownik pewną rzecz (nie napiszę jaką, bo szczerze zapomniałem, a nie jestem marketingowcem więc nie chcę się wygłupić) robił od 4-5 dni i klient za to płacił 6k. Dzięki AI robi dokładnie to samo w niecałą godzinę...

no to pięknie, tylko że teraz moim zdaniem mamy złoty czas w którym mamy możliwość wykonania czegoś szybko przy użyciu AI a skasowania jak za pracę ręczną. Te czasy się szybko skończą i niedługo klienci odkryją że mogą sobie to sami zrobić w godzinę, lub konkurencyjność sprowadzi to do stawki godzinowej.
Nikt nie będzie płacić na ms painta gotowego w 19s tyle samo co za podobny projekt napisany ręcznie. To raczej nie będzie tak że małe firmy będą nagle w stanie konkurować z gigantami tylko skala skomplikowania wszystkiego się znacznie zwiększy żeby zarobić tyle samo.
Skoro w godzinę za parę dolarów można teraz stworzyć reklamę wartą 0,5 mln USD to wszyscy mogą zrobić to samo i ta reklama już nie jest warta 0,5 mln USD a żeby się wybić niedługo będzie potrzeba czegoś znacznie lepszego bo tego typu reklamy niedługo będą robić podobne wrażenie jak te na regionalnych tvp.
Prezentacje w powerpoincie też kiedyś robiły duże wrażenie.

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
1

Masz rację, na razie porównujemy wytwory AI do tradycyjnej pracy i stąd się biorą te rozbieżności w cenach. I to się skończy, jak AI się upowszechni. Ja patrze na przyszłość swojego biznesu i błędem byłoby ignorować to co się wokół AI obecnie dzieje. Nie jestem programistą na etacie, tylko patrzę czy przetrwam na rynku w zmieniającym sie otoczeniu. Teraz jest właśnie odpowiedni moment aby podejmować strategiczne decyzje.

WeiXiao
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5226
1

@Panczo

To oczywiście mój punkt widzenia, ale wynika z doniesień prasowych o tym co robią najwięksi gracze

Doniesienia prasowe są nierzadko g**no warte, im dłużej inwestuje, im dłużej interesuje się daną branżą, im dłużej czytam sprawozdania firm, im dłużej śledze opinie pracowników danych firm itd. itd.,
to coraz bardziej widzę że większość newsów jest g**no warta, a w szczególności te, publikowane przez polskie outlety.

Cykl g**no-newsa:

  1. Plotka na twitterze wynikająca z niepoprawnej interpretacji czegoś lub niezrozumienia branży
  2. Artykuł w amerykańskim plotku na podstawie twittera
  3. Artykuł jest linkowany na twitterach/redditach
  4. Inne amerykańskie outlety piszą podobny artykuł jedynie modyfikując go minimalnie
  5. Polskie outlety robią copy+paste w translator i publikują to samo
  6. Za miesiąc okazuje się że to bajka :D
PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
0

@WeiXiao zatem któryś z nas się myli. Ja tu reprezentuję tylko i wyłącznie swoje opinie/przemyślenia. Jak masz inne to ok.

HA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1019
0

Staram się podchodzić do AI raczej sceptycznie. Nie sądzę, że w ciągu 2-3 lat nam zabierze prace, ale fakt, że postęp robi wrażenie.

Mnie zupełnie nie ruszają przykłady takiej jak tutaj podany, bo napisanie "jakiejść" aplikacji od zera to relatywnie prosty temat. Natomiast robi na mnie ogromne wrażenie, jak AI potrafi wyciągnąć informacje z projektu i na ich podstawie ulepić proste kawałki kodu, które mają sens - szczególnie gdy chodzi o customowy kod, a nie jakieś generyczne bebechy frameworka. Ostatnio miałem już kilka takich zdziwień, gdzie całkiem nietrywialne rzeczy potrafił sensownie zrobić. Daleko jeszcze do pisania biznesowego kodu w rozbudowanej aplikacji, ale przy umiejętnym użyciu mocno przyspiesza pracę. Wygląda na to, że w najbliższych latach będzie mocny shift z programistów na osoby ze skilem biznesowym. Najgorzej chyba będę miały osoby, które zajmują się programowaniem, ale nie wyszły na wysoki level i nie mają zdolności biznesowy/miękkich. Dla nich AI to powoli bezpośrednia konkurencja co już teraz widać po rynku juniorski. Po co mi junior, jak AI zrobi to samo szybciej.

crestfallen
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 57
1

Super, taki program napisze i 12latek. Dodajmy więc funkcjonalności które uchodzą za trudne w implementacji:

  • Zoom z możliwością rysowania przy powiększeniu
  • Dodawanie tekstu na obrazek (połączone z zoom'em zaczyna być ciekawe)
  • Zaznaczanie i wycinanie nieprostokątnych obszarów obrazu (lasso)

Mogę się założyć o pierwszej klasy 100 bitów i 2 bajty że AI się zakrztusi i wypluje jakieś farmazony...

pradoslaw
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
  • Postów: 216
1
crestfallen napisał(a):

Super, taki program napisze i 12latek. Dodajmy więc funkcjonalności które uchodzą za trudne w implementacji:

  • Zoom z możliwością rysowania przy powiększeniu
  • Dodawanie tekstu na obrazek (połączone z zoom'em zaczyna być ciekawe)
  • Zaznaczanie i wycinanie nieprostokątnych obszarów obrazu (lasso)

Mogę się założyć o pierwszej klasy 100 bitów i 2 bajty że AI się zakrztusi i wypluje jakieś farmazony...

Krótki opis nowych funkcji
Zoom: Suwak „Zoom” (50–300%) powiększa lub zmniejsza widok obu warstw (główny i tymczasowy). Funkcja getMousePos uwzględnia aktualny współczynnik powiększenia, dzięki czemu rysowanie działa poprawnie przy dowolnym zoomie.
Tekst: Po wybraniu narzędzia „Tekst” i kliknięciu w obszarze płótna pojawia się pole edycji. Po wpisaniu tekstu i naciśnięciu Enter, tekst jest rysowany na głównym canvasie.
Lasso: Po wybraniu narzędzia „Lasso” użytkownik może rysować nieregularną ścieżkę – podgląd zaznaczenia rysowany jest na czerwono. Po zakończeniu zaznaczania pojawia się komunikat potwierdzający wycięcie zaznaczenia – jeżeli użytkownik zatwierdzi, obszar wewnątrz lasso zostanie usunięty z obrazu (wycięty).

Miłego korzystania z aplikacji!

Kopiuj
<!DOCTYPE html>
<html lang="pl">
<head>
  <meta charset="UTF-8" />
  <title>Advanced MS Paint – 4programmers.net UI z Zoom, Tekstem i Lasso</title>
  <style>
    :root {
      --primary: #005792;       /* Niebieski inspirowany 4programmers.net */
      --secondary: #ffffff;
      --text: #ffffff;
      --background: #f5f5f5;
      --button-hover: #007ac1;
    }
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background-color: var(--background);
    }
    #toolbar {
      background-color: var(--primary);
      padding: 10px;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      color: var(--text);
    }
    #toolbar label {
      margin-right: 5px;
    }
    #toolbar > * {
      margin: 5px;
    }
    #toolbar input[type="range"] {
      vertical-align: middle;
    }
    #toolbar input[type="color"] {
      vertical-align: middle;
      border: none;
      padding: 0;
      width: 40px;
      height: 30px;
      background: var(--secondary);
      cursor: pointer;
    }
    #toolbar select {
      background: var(--secondary);
      color: #000;
      border: none;
      padding: 5px;
      border-radius: 3px;
      cursor: pointer;
    }
    #toolbar button {
      background: var(--secondary);
      color: var(--primary);
      border: none;
      padding: 8px 12px;
      border-radius: 3px;
      cursor: pointer;
      transition: background 0.3s;
    }
    #toolbar button:hover {
      background: var(--button-hover);
      color: var(--secondary);
    }
    /* Kontener płótna – ustalamy overflow:hidden, by transformacje nie powodowały niechcianych pasków */
    #canvasContainer {
      position: relative;
      border: 1px solid #ccc;
      margin: 10px auto;
      background-color: var(--secondary);
      width: 800px;
      height: 600px;
      overflow: hidden;
    }
    canvas {
      position: absolute;
      left: 0;
      top: 0;
      transform-origin: top left;
    }
  </style>
</head>
<body>
  <div id="toolbar">
    <label for="tool">Narzędzie:</label>
    <select id="tool">
      <option value="pencil">Ołówek</option>
      <option value="line">Linia</option>
      <option value="rectangle">Prostokąt</option>
      <option value="circle">Koło</option>
      <option value="eraser">Gumka</option>
      <option value="text">Tekst</option>
      <option value="lasso">Lasso</option>
    </select>

    <label for="brushSize">Grubość pędzla:</label>
    <input type="range" id="brushSize" min="1" max="50" value="5" />

    <label for="strokeColor">Kolor konturu:</label>
    <input type="color" id="strokeColor" value="#000000" />

    <label for="fillColor">Kolor wypełnienia:</label>
    <input type="color" id="fillColor" value="#000000" />

    <label for="fillShape">Wypełnienie:</label>
    <input type="checkbox" id="fillShape" />

    <label for="gradientFill">Wypełnienie gradientem:</label>
    <input type="checkbox" id="gradientFill" />

    <label for="gradientRotation">Rotacja gradientu (°):</label>
    <input type="range" id="gradientRotation" min="0" max="360" value="0" />

    <label for="zoomSlider">Zoom:</label>
    <input type="range" id="zoomSlider" min="50" max="300" value="100" />
    <span id="zoomValue">100%</span>

    <button id="clearCanvas">Wyczyść</button>
    <button id="saveCanvas">Zapisz</button>
    <button id="undo">Cofnij</button>
    <button id="redo">Wróć</button>
  </div>

  <div id="canvasContainer">
    <canvas id="canvas" width="800" height="600"></canvas>
    <canvas id="tempCanvas" width="800" height="600"></canvas>
  </div>

  <script>
    // Pobierz elementy i konteksty
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    const tempCanvas = document.getElementById('tempCanvas');
    const tempCtx = tempCanvas.getContext('2d');

    const canvasContainer = document.getElementById('canvasContainer');

    const toolSelect = document.getElementById('tool');
    const brushSize = document.getElementById('brushSize');
    const strokeColor = document.getElementById('strokeColor');
    const fillColor = document.getElementById('fillColor');
    const fillShape = document.getElementById('fillShape');
    const gradientFill = document.getElementById('gradientFill');
    const gradientRotation = document.getElementById('gradientRotation');
    const zoomSlider = document.getElementById('zoomSlider');
    const zoomValue = document.getElementById('zoomValue');
    const clearCanvasBtn = document.getElementById('clearCanvas');
    const saveCanvasBtn = document.getElementById('saveCanvas');
    const undoBtn = document.getElementById('undo');
    const redoBtn = document.getElementById('redo');

    let drawing = false;
    let currentTool = toolSelect.value;
    let startX = 0, startY = 0;
    let currentZoom = zoomSlider.value / 100;

    // Do obsługi narzędzia Lasso – zbiór punktów
    let lassoPoints = [];

    // Stosy do cofania i ponawiania zmian
    let undoStack = [];
    let redoStack = [];

    // Zapisz aktualny stan płótna
    function pushState() {
      redoStack = [];
      undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
    }

    function undo() {
      if (undoStack.length > 0) {
        redoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
        const state = undoStack.pop();
        ctx.putImageData(state, 0, 0);
      }
    }

    function redo() {
      if (redoStack.length > 0) {
        undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
        const state = redoStack.pop();
        ctx.putImageData(state, 0, 0);
      }
    }

    // Inicjalizacja – zapis pustego płótna
    pushState();

    // Aktualizacja wybranego narzędzia
    toolSelect.addEventListener('change', function () {
      currentTool = this.value;
    });

    // Obsługa zoomu – zmiana wartości zoom i transformacja canvasów
    zoomSlider.addEventListener('input', function() {
      currentZoom = zoomSlider.value / 100;
      zoomValue.textContent = zoomSlider.value + '%';
      canvas.style.transform = `scale(${currentZoom})`;
      tempCanvas.style.transform = `scale(${currentZoom})`;
    });

    // Funkcja pobierająca pozycję myszy – uwzględnia skalowanie
    function getMousePos(e) {
      const rect = tempCanvas.getBoundingClientRect();
      return {
        x: (e.clientX - rect.left) / currentZoom,
        y: (e.clientY - rect.top) / currentZoom
      };
    }

    // Obsługa zdarzeń myszy na canvasie tymczasowym
    tempCanvas.addEventListener('mousedown', startDrawing);
    tempCanvas.addEventListener('mousemove', draw);
    tempCanvas.addEventListener('mouseup', finishDrawing);
    tempCanvas.addEventListener('mouseout', finishDrawing);

    function startDrawing(e) {
      const pos = getMousePos(e);
      startX = pos.x;
      startY = pos.y;

      // Narzędzie Tekst – tworzymy pole input w miejscu kliknięcia
      if (currentTool === 'text') {
        let input = document.createElement("input");
        input.type = "text";
        input.style.position = "absolute";
        // Pozycjonowanie względem kontenera (używamy współrzędnych z eventu)
        const containerRect = canvasContainer.getBoundingClientRect();
        input.style.left = (e.clientX - containerRect.left) + "px";
        input.style.top = (e.clientY - containerRect.top) + "px";
        input.style.fontSize = (brushSize.value * currentZoom) + "px";
        input.style.border = "1px solid #000";
        canvasContainer.appendChild(input);
        input.focus();
        input.addEventListener("keydown", function(event) {
          if (event.key === "Enter") {
            let text = input.value;
            ctx.font = `${brushSize.value}px Arial`;
            ctx.fillStyle = fillColor.value;
            ctx.fillText(text, startX, startY);
            canvasContainer.removeChild(input);
            pushState();
          }
        });
        return;
      }

      // Narzędzie Lasso – inicjujemy zbiór punktów
      if (currentTool === 'lasso') {
        lassoPoints = [];
        lassoPoints.push({x: pos.x, y: pos.y});
        drawing = true;
        return;
      }

      drawing = true;
      // Dla ołówka i gumki – rozpoczynamy ścieżkę
      if (currentTool === 'pencil' || currentTool === 'eraser') {
        ctx.beginPath();
        ctx.moveTo(startX, startY);
      }
    }

    function draw(e) {
      if (!drawing) return;
      const pos = getMousePos(e);
      const mouseX = pos.x;
      const mouseY = pos.y;

      if (currentTool === 'pencil') {
        ctx.lineTo(mouseX, mouseY);
        ctx.strokeStyle = strokeColor.value;
        ctx.lineWidth = brushSize.value;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.stroke();
      } else if (currentTool === 'eraser') {
        ctx.globalCompositeOperation = 'destination-out';
        ctx.lineTo(mouseX, mouseY);
        ctx.strokeStyle = 'rgba(0,0,0,1)';
        ctx.lineWidth = brushSize.value;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.stroke();
        ctx.globalCompositeOperation = 'source-over';
      } else if (currentTool === 'line') {
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        tempCtx.lineWidth = brushSize.value;
        tempCtx.strokeStyle = strokeColor.value;
        tempCtx.lineCap = 'round';
        tempCtx.beginPath();
        tempCtx.moveTo(startX, startY);
        tempCtx.lineTo(mouseX, mouseY);
        tempCtx.stroke();
      } else if (currentTool === 'rectangle') {
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        tempCtx.lineWidth = brushSize.value;
        tempCtx.strokeStyle = strokeColor.value;
        tempCtx.fillStyle = fillColor.value;
        if (fillShape.checked) {
          if (gradientFill.checked) {
            let width = mouseX - startX;
            let height = mouseY - startY;
            let cx = startX + width / 2;
            let cy = startY + height / 2;
            let halfDiagonal = Math.sqrt(width * width + height * height) / 2;
            let angle = gradientRotation.value * Math.PI / 180;
            let x0 = cx - halfDiagonal * Math.cos(angle);
            let y0 = cy - halfDiagonal * Math.sin(angle);
            let x1 = cx + halfDiagonal * Math.cos(angle);
            let y1 = cy + halfDiagonal * Math.sin(angle);
            let grad = tempCtx.createLinearGradient(x0, y0, x1, y1);
            grad.addColorStop(0, fillColor.value);
            grad.addColorStop(1, strokeColor.value);
            tempCtx.fillStyle = grad;
            tempCtx.fillRect(startX, startY, width, height);
          } else {
            tempCtx.fillRect(startX, startY, mouseX - startX, mouseY - startY);
          }
        }
        tempCtx.strokeRect(startX, startY, mouseX - startX, mouseY - startY);
      } else if (currentTool === 'circle') {
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        let radius = Math.sqrt(Math.pow(mouseX - startX, 2) + Math.pow(mouseY - startY, 2));
        tempCtx.lineWidth = brushSize.value;
        tempCtx.strokeStyle = strokeColor.value;
        tempCtx.beginPath();
        tempCtx.arc(startX, startY, radius, 0, Math.PI * 2);
        if (fillShape.checked) {
          tempCtx.fillStyle = fillColor.value;
          tempCtx.fill();
        }
        tempCtx.stroke();
      } else if (currentTool === 'lasso') {
        // Dodajemy punkty do ścieżki lasso i rysujemy podgląd
        lassoPoints.push({x: mouseX, y: mouseY});
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        tempCtx.beginPath();
        tempCtx.moveTo(lassoPoints[0].x, lassoPoints[0].y);
        for (let i = 1; i < lassoPoints.length; i++) {
          tempCtx.lineTo(lassoPoints[i].x, lassoPoints[i].y);
        }
        tempCtx.strokeStyle = 'red';
        tempCtx.lineWidth = 1;
        tempCtx.stroke();
      }
    }

    function finishDrawing(e) {
      if (!drawing) return;
      const pos = getMousePos(e);
      const mouseX = pos.x;
      const mouseY = pos.y;

      if (currentTool === 'pencil' || currentTool === 'eraser') {
        // Dla tych narzędzi kończymy ścieżkę bez dodatkowych operacji
      } else if (currentTool === 'line') {
        ctx.lineWidth = brushSize.value;
        ctx.strokeStyle = strokeColor.value;
        ctx.lineCap = 'round';
        ctx.beginPath();
        ctx.moveTo(startX, startY);
        ctx.lineTo(mouseX, mouseY);
        ctx.stroke();
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
      } else if (currentTool === 'rectangle') {
        ctx.lineWidth = brushSize.value;
        ctx.strokeStyle = strokeColor.value;
        if (fillShape.checked) {
          if (gradientFill.checked) {
            let width = mouseX - startX;
            let height = mouseY - startY;
            let cx = startX + width / 2;
            let cy = startY + height / 2;
            let halfDiagonal = Math.sqrt(width * width + height * height) / 2;
            let angle = gradientRotation.value * Math.PI / 180;
            let x0 = cx - halfDiagonal * Math.cos(angle);
            let y0 = cy - halfDiagonal * Math.sin(angle);
            let x1 = cx + halfDiagonal * Math.cos(angle);
            let y1 = cy + halfDiagonal * Math.sin(angle);
            let grad = ctx.createLinearGradient(x0, y0, x1, y1);
            grad.addColorStop(0, fillColor.value);
            grad.addColorStop(1, strokeColor.value);
            ctx.fillStyle = grad;
            ctx.fillRect(startX, startY, mouseX - startX, mouseY - startY);
          } else {
            ctx.fillStyle = fillColor.value;
            ctx.fillRect(startX, startY, mouseX - startX, mouseY - startY);
          }
        }
        ctx.strokeRect(startX, startY, mouseX - startX, mouseY - startY);
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
      } else if (currentTool === 'circle') {
        let radius = Math.sqrt(Math.pow(mouseX - startX, 2) + Math.pow(mouseY - startY, 2));
        ctx.lineWidth = brushSize.value;
        ctx.strokeStyle = strokeColor.value;
        ctx.beginPath();
        ctx.arc(startX, startY, radius, 0, Math.PI * 2);
        if (fillShape.checked) {
          ctx.fillStyle = fillColor.value;
          ctx.fill();
        }
        ctx.stroke();
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
      } else if (currentTool === 'lasso') {
        // Kończymy zaznaczanie – rysujemy zamkniętą ścieżkę
        drawing = false;
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        tempCtx.beginPath();
        tempCtx.moveTo(lassoPoints[0].x, lassoPoints[0].y);
        for (let i = 1; i < lassoPoints.length; i++) {
          tempCtx.lineTo(lassoPoints[i].x, lassoPoints[i].y);
        }
        tempCtx.closePath();
        tempCtx.strokeStyle = 'red';
        tempCtx.lineWidth = 1;
        tempCtx.stroke();
        // Zapytaj użytkownika, czy wyciąć zaznaczony obszar
        if (confirm("Wyciąć zaznaczenie?")) {
          ctx.save();
          ctx.beginPath();
          ctx.moveTo(lassoPoints[0].x, lassoPoints[0].y);
          for (let i = 1; i < lassoPoints.length; i++) {
            ctx.lineTo(lassoPoints[i].x, lassoPoints[i].y);
          }
          ctx.closePath();
          ctx.clip();
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.restore();
          pushState();
        }
        tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
        lassoPoints = [];
        return;
      }
      drawing = false;
      pushState();
    }

    clearCanvasBtn.addEventListener('click', function () {
      pushState();
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      pushState();
    });

    saveCanvasBtn.addEventListener('click', function () {
      const mergedCanvas = document.createElement('canvas');
      mergedCanvas.width = canvas.width;
      mergedCanvas.height = canvas.height;
      const mergedCtx = mergedCanvas.getContext('2d');
      mergedCtx.drawImage(canvas, 0, 0);
      mergedCtx.drawImage(tempCanvas, 0, 0);
      const link = document.createElement('a');
      link.download = 'my_painting.png';
      link.href = mergedCanvas.toDataURL();
      link.click();
    });

    undoBtn.addEventListener('click', undo);
    redoBtn.addEventListener('click', redo);
  </script>
</body>
</html>


HA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 79
4

@pradoslaw rysowanie na zoomie działa spoko, natomiast pole tekstowe jest przyklejone na stałe. Dodaj sobie tekst i potem zrób zoom - tekst zostanie w tym samym miejscu i będzie miał taki sam rozmiar. Teraz z zoomem dodaj pole tekstowe - będzie dużo większe i zostanie takich samych rozmiarów gdy zooma zresetujesz. Do tego nie da się rysować po tekście. Wycinanie tekstu lassem nie działa.

Sam bawiłem się ostatnio zestawem Cursor + Composer i faktycznie jest w stanie mega szybko zakodzić coś działającego, ale dopieszczanie szczegółów wręcz graniczy z cudem, a czasem musiałem i tak mu dawać konkretne, techniczne instrukcje - np. wymiękał, gdy kazałem mu zrobić prosty layout z 3 kolumnami, gdzie środkowa jest rozepchana względem treści, a boczne dzielą się pozostałym miejscem po równo, a kontent w każdej kolumnie jest wyśrodkowany. Ogarnął dopiero jak mu kazałem zrobić flexboksa o konkretnych parametrach.

crestfallen
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 57
1

Zoom+Tekst - porażka.
Lasso - działa lepiej niż myślałem.

Jak zawsze problem jest w szczegółach - gdy myślałem o dodawaniu tekstu to liczyłem na dialog box z wyborem czcionki, krojów pisma, cieniem.

Powiem tak lepiej niż myślałem, to już nie 12 latek ale słaby student pierwszego roku IT!

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
0
pradoslaw napisał(a):

Miłego korzystania z aplikacji!

byłoby miło gdybyśmy nie musieli tego przeklejać do pliku żeby uruchomić. Może poprosisz chat żeby dopisał tu opcję odpalania snippetów HTML/CSS/JS wewnątrz strony tak jak na stackoverflow

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
1
crestfallen napisał(a):

Lasso - działa lepiej niż myślałem.

No cóż, nie ma tu nic trudnego, po prostu zamalowuje ścieżkę z punktów

Kopiuj
if (confirm("Wyciąć zaznaczenie?")) {
          ctx.save();
          ctx.beginPath();
          ctx.moveTo(lassoPoints[0].x, lassoPoints[0].y);
          for (let i = 1; i < lassoPoints.length; i++) {
            ctx.lineTo(lassoPoints[i].x, lassoPoints[i].y);
          }
          ctx.closePath();
          ctx.clip();

nie ma tu żadnego algorytmu który musi znaleźć elementy czy piksele w środku, to wszystko już jest wbudowane w canvas api. Przesuwanie byłoby już chyba trudniejsze.

To polećmy może już po całości, niech doda warstwy, elementy wektorowe z możliwością rasteryzacji i zaznaczanie lassem tych elementów, przesuwanie i zmiana rozmiarów tych elementów.

Spine
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 6967
0

@pradoslaw: Gratulacje!
Twoja pierwsza apka?

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.