OpenCV w Javie feat. Maven

Ostatnim razem pisałem o Mavenie – jeżeli jeszcze nie przeczytałeś tego wpisu, to zapraszam – możesz kliknąć tutaj. Chciałbym, żebyśmy dzisiaj przećwiczyli w praktyce, to co opisałem ostatnio. Tym razem dodatkowo cofniemy się o niemal dwa miesiące. Pod koniec sierpnia zamieściłem pierwszy wpis o bibliotece OpenCV. Wtedy pisałem, że prezentowane podejście do tworzenia projektów jest niepraktykowane, że w rzeczywistości stosuje się takie narzędzie jak Maven (lub Gradle). I dziś trochę urzeczywistnimy nasz projekt wykorzystujący bibliotekę OpenCV.

Konfiguracja

Zanim przejdziemy do tworzenia pierwszego projektu przy użyciu IDE (Zintegrowanego Środowiska Programistycznego), chciałbym Wam pokazać kilka przydatnych ustawień w IntelliJ (bo to właśnie jest moje ulubione IDE).

Instalacja IntelliJ jest na tyle prosta, że nie będę zamieszczał tutaj zrzutów ekranu i nie przejdziemy wspólnie przez proces instalacji. Wierzę, że sobie poradzisz!

Jak już zainstalujesz IntelliJ i uruchomisz go po raz pierwszy, to najpierw będziesz musiał odpowiedzieć na kilka pytań. Pierwsze z nich dotyczy wysyłania anonimowych danych do formy JetBrains (twórcy IntelliJ):

Ja z reguły nie wysyłam takich danych. Może przez to nie jestem ich ulubionym użytkownikiem. Następnie zapyta, czy nie zaimportować ustawień programu, które stworzyła poprzednia wersja

Najprawdopodobniej nie masz takich ustawień, więc nie importujemy niczego i przechodzimy dalej. Teraz możesz wybrać motyw interfejsu użytkownika.

Ten wybór często jest dyskutowany pomiędzy współpracownikami. Ja wolę ten jasny wygląd. Choć niektórzy twierdzą, że przy ciemnym wyglądzie mniej się męczą oczy. Następnie możemy wybrać domyślne dodatki (plug-iny), które chcemy mieć w naszej instalacji IntelliJ:

Ja tutaj z reguły pozostawiam domyślne ustawienia i przechodzę dalej. Później możemy od razu doinstalować dodatkowe dodatki:

Warto wybrać sobie Key Promoter X oraz IDE Features Trainer, dzięki niemu nauczysz się lepiej wykorzystywać możliwości swojego IDE. Następnie pojawi Ci się następujące okienko:

Tak jak napisałem przed chwilą, przejdźmy do ustawień, czyli Configure. Po kliknięciu tego przycisku pojawi się rozwijane menu, w nim wybierz Settings. Zobaczysz następujące okienko:

 

Java

Powinna zostać wykryta automatycznie. Ponieważ IntelliJ nie jest dystrybuowany z żadnym JDK.

Maven

IntelliJ jest dystrybuowany z wbudowanym Mavenem. Ale w związku z tym, że już zaczęliśmy używać własnej zainstalowanej wersji wskażmy IntelliJ-owi, którego Mavena ma używać. W tym celu wybieramy w menu z lewej strony Build, Execution, Deployment i w podmenu Build Tools i następnie Maven. Zobaczymy:

W środkowej części okienka widać Maven home directory. Jeżeli nie widzisz swojej instalacji, możesz wskazać katalog główny Mavena po naciśnięciu przycisku z trzema kropkami. Gdy wybierzesz właściwą opcję nie zapomnij nacisnąć przycisku Apply, żeby zapisać swoje zmiany.

Terminal

Jak już miałeś okazję się przekonać, lubię używać linii poleceń. IntelliJ ma opcję uruchomienia linii poleceń wewnątrz swojego okna. Domyślnie jest to program cmd.exe (czyli nielubiana przeze mnie konsola Windowsa). Sugeruję zmienić ją na ten wiersz poleceń dostarczany z programem Git.

W oknie ustawień wybierz z menu po lewej stronie Tools i następnie Terminal. Zobaczysz następujące okienko:

Kliknij na ikonę otworzonego folderu i wskaż plik bash.exe, który jest w katalogu bin katalogu, w którym zainstalowałeś Gita:

 

Wydaje mi się, że to są dwa ustawienia, które powinieneś zmienić.

 

Stworzenie projektu

W ostatnim wpisie tworzyliśmy projekt przy wykorzystaniu linii poleceń. Jak się pewnie domyślasz – w praktyce dużo częściej wykorzystuje się do tego IDE. Pokażę, jak można szybko wyklikać nowy projekt Mavena przy użyciu IntelliJ. W okienku

Wybierz opcję New Project. Po kliknięciu zobaczysz takie okno:

Jeżeli w górnej części okienka nie widzisz numeru wersji JDK (przy napisie Project SDK) musisz wskazać tę wersję Javy, z której chcesz korzystać. Instrukcję instalacji OpenJDK znajdziesz w moim poprzednim wpisie. Chcemy stworzyć projekt Mavena, więc po lewej stronie wybieramy pozycję Maven. Wczytywanie listy dostępnych archetypów może chwilę zająć. Ale gdy lista się zapełni to zobaczysz coś takiego:

Oczywiście chcemy wykorzystać archetyp, więc zaznaczamy odpowiednią opcję w oknie i następnie na liście szukamy archetypu maven-archetype-quickstart:

Po wyborze wersji RELEASE klikamy Next. W następnym oknie mamy początkowo do wyboru tylko nazwę projektu i to, gdzie chcemy go zapisać:

Ale możemy rozwinąć ukrytą część okienka i zobaczymy możliwość podania współrzędnych naszego projektu:

Wystarczy, że podasz nazwę swojego projektu (ja podałem pictures-manipulation-3) – to ustawi odpowiednią lokalizację projektu, oraz artifactId. Wystarczy, że podasz jeszcze groupId i wszystko jest gotowe:

Pojawi Ci się jeszcze okienko, żebyś mógł sobie wszystko sprawdzić:

Stworzenie nowego projektu chwilę potrwa, ale ostatecznie zobaczysz coś takiego:

Na samym początku lubię trochę zmienić widok tego okienka. Po kliknięciu View pojawia się menu, w którym wchodzę w Apperance i następnie klikam w przycisk Toolbar. Teraz okienko wygląda tak, jak lubię:

Projekt, który się wygenerował ma następującą strukturę katalogów:

Odrobinę się różni, od tego co widzieliśmy w poprzednim wpisie. IntelliJ dodał od siebie katalog .idea oraz plik pictures-manipulation-3.iml. Przyznam się (choć zabrzmi to niezbyt profesjonalnie), że nigdy nie zagłębiałem się w to, co jest w tych plikach. Oczywiście pliki z kodem źródłowym wyglądają zupełnie tak samo jak w poprzednim wpisie. Czyli App.java ma poniższą zawartość:

package pl.backlog.green;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}

plik AppTest.java:

package pl.backlog.green;

import static org.junit.Assert.assertTrue;

import org.junit.Test;

/**
 * Unit test for simple App.
 */
public class AppTest 
{
    /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue()
    {
        assertTrue( true );
    }
}

no i oczywiście plik pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>pl.backlog.green</groupId>
  <artifactId>pictures-manipulation-3</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>pictures-manipulation-3</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Zacznijmy od uproszczenia projektu. Usuwamy katalog test, usuwamy plik App.java i upraszczamy plik pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>pl.backlog.green</groupId>
  <artifactId>pictures-manipulation-3</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>pictures-manipulation-3</name>
  <url>https://zielony-backlog.pl</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>14</maven.compiler.source>
    <maven.compiler.target>14</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Teraz okno z projektem wygląda następująco:

Dodanie biblioteki OpenCV

Z projektu pictures-manipulation-1, który opisałem w moim poprzednim wpisie skopiuj wszystkie pliki źródłowe. Skopiuj też katalog resources do katalogu src w skatalogu main. Teraz okno z projektem będzie wyglądać jakoś tak:

Te czerwone podkreślenia nazw klas oznaczają, że w klasach znajdują się błędy kompilacji. Podobnie w zawartości plików tekstowych czerwonym kolorem zaznaczone są nazwy klas, których kompilator nie rozpoznaje. To przecież oczywiste! Nie dodaliśmy pliku jar, który zawiera bibliotekę OpenCV. Całe szczęście nie musimy tego robić!:) Od tego mamy Mavena, żeby sam pobrał i użył brakujących zależności. Musimy dodać odpowiedni wpis do pliku pom.xml. Skąd mamy wiedzieć, jaki?

Aby tego się dowiedzieć, odwiedzamy stronę repozytorium Mavena: https://mvnrepository.com/. I w okienku wyszukiwania wpisujemy opencv:

Wybieramy drugą pozycję na liście. Pierwsza pozycja jest bardzo interesująca, ale z pewnych względów wrócę do niej w jednym z następnych wpisów. Po wyborze drugiej pozycji (OpenPnP OpenCV) widzimy następujące okno:

Z reguły dobrym pomysłem jest użycie najnowszej wersji biblioteki, więc wybieramy wersję 4.3.0-2:

I teraz już dokładnie wiemy, jaką zależność musimy dodać do naszego pliku pom.xml. Dodajemy ten fragment XMLa wewnątrz znacznika <dependencies>:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>pl.backlog.green</groupId>
  <artifactId>pictures-manipulation-3</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>pictures-manipulation-3</name>
  <url>https://zielony-backlog.pl</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>14</maven.compiler.source>
    <maven.compiler.target>14</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.openpnp</groupId>
      <artifactId>opencv</artifactId>
      <version>4.3.0-2</version>
    </dependency>
  </dependencies>
</project>

Zaraz po wklejeniu tego znacznika, to co wkleiliśmy będzie się świeciło na czerwono. Dlatego, że Maven nie zna tej zależności. Musimy ją teraz pobrać z Internetu w taki sposób, żeby kompilator był świadom, gdzie jest odpowiedni plik jar.

Z prawej strony okno widać napis Maven, gdy w niego klikniesz zobaczysz dodatkowy panel w oknie IntelliJ:

Widzimy, że mamy do wyboru Główny Cykl Życia projektu, oraz dostępne plug-iny Mavena (dla plug-inów możemy sobie podejrzeć, jakie goale udostępniają)

Dla nas najbardziej interesująca będzie faza package. Kliknij na nią dwukrotnie. Proces budowania paczki, chwilę potrwa (muszą się ściągnąć potrzebne artefakty). Ale po zakończeniu pracy Mavena zobaczysz coś podobnego:

W drzewku projektu widać katalog target z oczekiwaną zawartością. W pliku pom.xml dodana zależność nie świeci się na czerwono. Pliki źródłowe wciąż się świecą na czerwono. Aby zsynchronizować IntelliJ z Mavenem musisz kliknąć na ten przycisk:

Po chwili wszystko się kompiluje.

Spróbujmy uruchomić jeden z naszych programów CreateWatermark:

Najprościej jest nacisnąć zielony trójkąt tutaj:

Albo ten odrobinę niżej. Po kliknięciu rozwija się małe menu, aby po prostu uruchomić ten program wybierz pierwszą opcję:

Uruchomienie się nie powiedzie. Dostaniemy następujący błąd wykonania programu:

Pamiętasz? Poprzednim razem, musieliśmy dodać opcję do maszyny wirtualnej Javy. Teraz nie musimy tego robić, gdyż to Maven za nas odwala całą brudną robotę. Wystarczy więc, że linijkę:

System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

zastąpimy przez:

nu.pattern.OpenCV.loadLocally();

Program się uruchomi, ale nie powstanie nigdzie plik watermark.png. Dlaczego? Bo nie istnieje ścieżka resources. Ale wystarczy, że zastąpimy linię:

Imgcodecs.imwrite("resources/watermark.png", image);

przez

Imgcodecs.imwrite("src/main/resources/watermark.png", image);

to program się uruchomi i wygeneruje prawidłowy plik:

Ostatecznie dostaniemy następującą klasę:

public class CreateWatermark {
    public static void main(String[] args) {
        nu.pattern.OpenCV.loadLocally();

        Mat image = new Mat(200, 200, CvType.CV_8UC4, new Scalar(0,0,0,0));

        String text = "Doktor Ziel";
        Point position = new Point(10, 110);
        Scalar color = new Scalar(90, 60, 90, 255);
        int font = Imgproc.FONT_HERSHEY_SIMPLEX;
        int scale = 1;
        int thickness = 4;
        Imgproc.putText(image,text, position, font, scale, color, thickness);

        Imgcodecs.imwrite("src/main/resources/watermark.png", image);
    }
}

Podobnie musimy zaktualizować pozostałe klasy:

public class ScaleInputImage {
    public static void main(String[] args) {
        nu.pattern.OpenCV.loadLocally();

        Mat inputImage = Imgcodecs.imread("src/main/resources/inputImage.jpg");
        Size inSize = inputImage.size();

        Mat outputImage = new Mat();
        Size outSize = new Size(inSize.width/3, inSize.height/3);

        Imgproc.resize(inputImage, outputImage, outSize);
        Imgcodecs.imwrite("src/main/resources/outputImage.jpg", outputImage);
    }
}
public class PutWatermarkOnImage {
    public static void main(String[] args) {
        nu.pattern.OpenCV.loadLocally();

        Mat image = imread("src/main/resources/inputImage.jpg", IMREAD_COLOR);
        cvtColor(image, image, COLOR_BGR2BGRA);

        Mat watermark = imread("src/main/resources/watermark.png", IMREAD_UNCHANGED);
        resize(watermark, watermark, image.size());

        Mat result = new Mat();
        addWeighted(image, 1, watermark, 1, 0, result);
        imwrite("src/main/resources/finalImage.png", result);
    }
}
public class PutWatermarkInTheRightBottomCorner {
    public static void main(String[] args) {
        nu.pattern.OpenCV.loadLocally();

        Mat image = imread("src/main/resources/inputImage.jpg", IMREAD_COLOR);
        cvtColor(image, image, COLOR_BGR2BGRA);

        Mat watermark = imread("src/main/resources/watermark.png", IMREAD_UNCHANGED);
        Mat transparentLayer = new Mat(image.rows(), image.cols(), CvType.CV_8UC4);
        Rect roi = new Rect(
                image.cols() - watermark.cols(),
                image.rows() - watermark.rows(),
                watermark.cols(),
                watermark.rows());
        watermark.copyTo(transparentLayer.submat(roi));

        Mat result = new Mat();
        addWeighted(image, 1, transparentLayer, 1, 0, result);
        imwrite("src/main/resources/finalImage.png", result);
    }
}

Oraz ostateczna klasa:

public class Watermak {
    public static void main(String[] args) {
        nu.pattern.OpenCV.loadLocally();

        String imagePath = args[0];
        String watermarkPath = args[1];
        String finalImagePath = args[2];

        Mat image = Imgcodecs.imread(imagePath, IMREAD_COLOR);
        Size inSize = image.size();
        Size outSize = new Size(inSize.width/2.5, inSize.height/2.5);

        resize(image, image, outSize);
        cvtColor(image, image, COLOR_BGR2BGRA);

        Mat watermark = imread(watermarkPath, IMREAD_UNCHANGED);
        Mat transparentLayer = new Mat(outSize, CvType.CV_8UC4);
        Rect roi = new Rect(
                image.cols() - watermark.cols() - 10,
                image.rows() - watermark.rows() - 10,
                watermark.cols(),
                watermark.rows());
        watermark.copyTo(transparentLayer.submat(roi));

        Mat result = new Mat();
        addWeighted(image, 1, transparentLayer, 0.35, 0, result);
        imwrite(finalImagePath, result);
    }
}

Oczywiście cały kod źródłowy możesz znaleźć na moim githubie.

Wygodne uruchamianie programu

Tak jak w ostatnim wpisie, możemy dodać plug-in maven-assembly-plugin:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
          <configuration>
            <archive>
              <manifest>
                <mainClass>
                  pl.backlog.green.Watermak
                </mainClass>
              </manifest>
            </archive>
            <descriptorRefs>
              <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Aby w czasie wykonywania fazy package utworzył się nam plik wykonywalny:

pawel@DESKTOP-0BB9FUV MINGW64 ~/IdeaProjects/pictures-manipulation-3
$ ls target/
archive-tmp  generated-sources  maven-status                                                    pictures-manipulation-3-1.0-SNAPSHOT.jar
classes      maven-archiver     pictures-manipulation-3-1.0-SNAPSHOT-jar-with-dependencies.jar

Teraz ten plik wykonywalny przekopiuję do katalogu bin w moim katalogu domowym. Oczywiście najpierw muszę stworzyć ten katalog, bo jeszcze go tam nie mam:

pawel@DESKTOP-0BB9FUV MINGW64 ~/IdeaProjects/pictures-manipulation-3
$ mkdir ~/bin

pawel@DESKTOP-0BB9FUV MINGW64 ~/IdeaProjects/pictures-manipulation-3
$ cp target/pictures-manipulation-3-1.0-SNAPSHOT-jar-with-dependencies.jar ~/bin/pictures-manipulation-3.jar

pawel@DESKTOP-0BB9FUV MINGW64 ~/IdeaProjects/pictures-manipulation-3
$ ls ~/bin/
pictures-manipulation-3.jar

Następnie wewnątrz katalogu bin w moim katalogu domowym dodaję plik watermark.sh:

#!/bin/bash

java -jar /c/Users/pawel/bin/pictures-manipulation-3.jar $@

To jest bardzo prosty skrypt powłoki linuksa. Po prostu za pomocą programu java uruchamia nasz wykonywalny plik jar. Ta kombinacja znaków dolar + małpka oznacza, że wszystkie argumenty uruchomienia skryptu są przekazywane do programu Javowego. O samych skryptach powłoki linuksa opowiem w innym wpisie.

Dlaczego wybraliśmy katalog bin w katalogu domowym? Dlatego, że linia poleceń linuksa automatycznie dodaje ten katalog do zmiennej PATH, czyli tej zmiennej która zawiera katalogi, które są przeszukiwane w celu znalezienia pliku wykonywalnego w momencie wydania polecenia w terminalu. Gdy w terminalu wpiszemy po prostu watermark.sh:

pawel@DESKTOP-0BB9FUV MINGW64 /
$ ls
bin/  etc/           LICENSE.txt  ReleaseNotes.html  unins000.exe*
cmd/  git-bash.exe*  mingw64/     tmp/               unins000.msg
dev/  git-cmd.exe*   proc/        unins000.dat       usr/

pawel@DESKTOP-0BB9FUV MINGW64 /
$ watermark.sh
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out
 of bounds for length 0
        at pl.backlog.green.Watermak.main(Watermak.java:17)

to terminal linuksa, przejrzy wszystkie katalogi na zmiennej PATH i w każdym z nich będzie próbował znaleźć plik watermark.sh. A dzięki pierwszej linijce tego skryptu, Linuks wie, jak uruchomić ten plik i traktuje ten skrypt jako plik wykonywalny. Dzięki temu, gdy w katalogu będę miał dwa pliki graficzne:

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ ls
IMG_0601.JPG  watermarkWhite.png

Niezależnie od ich położenia mogę uruchomić mój skrypt:

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ ls
IMG_0601.JPG  watermarkWhite.png

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ watermark.sh IMG_0601.JPG watermarkWhite.png mak.jpg

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ ls
IMG_0601.JPG  mak.jpg  watermarkWhite.png

Który wygeneruje mi następujący obrazek

 

 

Wyświetl ten post na Instagramie.

 

Post udostępniony przez Doktor Ziel (@doktor_ziel)

Podsumowanie

Oczywiście ten program jest dość prymitywny. Przede wszystkim problematyczne jest to podawanie argumentów uruchomienia – należy pamiętać w jakiej kolejności podać pliki. Może przy trzech ścieżkach nie jest to duży problem, ale przy większej liczbie argumentów to może okazać się trudne. Rozwiązanie tego problemu planuję pokazać już w najbliższym czasie, więc śledź mnie we wszystkich możliwych mediach społecznościowych, bo nowy wpis pojawi się szybciej niż myślisz.

Aspirujący twórca internetowy, który zna się na programowaniu i chce się dzielić wiedzą

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Scroll to top